1 /**
2 Copyright: Copyright (c) 2016 Andrey Penechko.
3 License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
4 Authors: Andrey Penechko.
5 */
6 module voxelman.world.db.sqliteworlddb;
7 
8 public import sqlite.d2sqlite3;
9 
10 import std.array : uninitializedArray;
11 import std.conv;
12 import std.stdio;
13 import voxelman.world.storage.coordinates : ChunkWorldPos;
14 import voxelman.utils.textformatter;
15 
16 alias StatementHandle = size_t;
17 enum USE_WAL = false;
18 
19 struct SqliteWorldDb
20 {
21 	private Database db;
22 
23 	Statement perWorldInsertStmt;
24 	Statement perWorldSelectStmt;
25 	Statement perWorldDeleteStmt;
26 	Statement* statementToReset;
27 
28 	//Statement perDimentionInsertStmt;
29 	//Statement perDimentionSelectStmt;
30 	//Statement perDimentionDeleteStmt;
31 
32 	Statement perChunkInsertStmt;
33 	Statement perChunkSelectStmt;
34 	Statement perChunkDeleteStmt;
35 
36 	private Statement[] statements;
37 
38 	//-----------------------------------------------
39 	void open(string filename)
40 	{
41 		db = Database(filename);
42 
43 		static if (USE_WAL) {
44 			db.execute("PRAGMA synchronous = normal");
45 			db.execute("PRAGMA journal_mode = wal");
46 		} else {
47 			db.execute("PRAGMA synchronous = off");
48 			db.execute("PRAGMA journal_mode = memory");
49 		}
50 		db.execute("PRAGMA count_changes = off");
51 
52 		db.execute("PRAGMA temp_store = memory");
53 		db.execute(`PRAGMA page_size = "4096"; VACUUM`);
54 
55 		db.execute(perWorldTableCreate);
56 		//db.execute(perDimentionTableCreate);
57 		db.execute(perChunkTableCreate);
58 
59 		perWorldInsertStmt = db.prepare(perWorldTableInsert);
60 		perWorldSelectStmt = db.prepare(perWorldTableSelect);
61 		perWorldDeleteStmt = db.prepare(perWorldTableDelete);
62 
63 		//perDimentionInsertStmt = db.prepare(perDimentionTableInsert);
64 		//perDimentionSelectStmt = db.prepare(perDimentionTableSelect);
65 		//perDimentionDeleteStmt = db.prepare(perDimentionTableDelete);
66 
67 		perChunkInsertStmt = db.prepare(perChunkTableInsert);
68 		perChunkSelectStmt = db.prepare(perChunkTableSelect);
69 		perChunkDeleteStmt = db.prepare(perChunkTableDelete);
70 	}
71 
72 	void close()
73 	{
74 		if (statementToReset) statementToReset.reset();
75 		destroy(perWorldInsertStmt);
76 		destroy(perWorldSelectStmt);
77 		destroy(perWorldDeleteStmt);
78 		//destroy(perDimentionInsertStmt);
79 		//destroy(perDimentionSelectStmt);
80 		//destroy(perDimentionDeleteStmt);
81 		destroy(perChunkInsertStmt);
82 		destroy(perChunkSelectStmt);
83 		destroy(perChunkDeleteStmt);
84 		foreach(ref s; statements)
85 			destroy(s);
86 		destroy(statements);
87 		//db.close();
88 	}
89 
90 	//-----------------------------------------------
91 	// key should contain only alphanum chars and .
92 	void savePerWorldData(string key, ubyte[] data)
93 	{
94 		perWorldInsertStmt.inject(key, data);
95 	}
96 
97 	// Reset statement after returned data is no longer needed
98 	ubyte[] loadPerWorldData(string key)
99 	{
100 		if (statementToReset) statementToReset.reset();
101 		statementToReset = &perWorldSelectStmt;
102 		perWorldSelectStmt.bindAll(key);
103 		auto result = perWorldSelectStmt.execute();
104 		if (result.empty) return null;
105 		return result.front.peekNoDup!(ubyte[])(0);
106 	}
107 	void removePerWorldData(string key)
108 	{
109 		perWorldDeleteStmt.inject(key);
110 	}
111 
112 	//void savePerDimentionData(string key, int dim, ubyte[] data)
113 
114 	//ubyte[] loadPerDimentionData(string key, int dim)
115 	import voxelman.core.config;
116 	void savePerChunkData(ulong cwp, ubyte[] data)
117 	{
118 		perChunkInsertStmt.inject(cast(long)cwp, data);
119 	}
120 
121 	// Reset statement after returned data is no longer needed
122 	ubyte[] loadPerChunkData(ulong cwp)
123 	{
124 		if (statementToReset) statementToReset.reset();
125 		statementToReset = &perChunkSelectStmt;
126 		perChunkSelectStmt.bindAll(cast(long)cwp);
127 		auto result = perChunkSelectStmt.execute();
128 		if (result.empty) return null;
129 		return result.front.peekNoDup!(ubyte[])(0);
130 	}
131 
132 	//-----------------------------------------------
133 	void beginTxn() {
134 	}
135 	void abortTxn() {
136 	}
137 	void commitTxn() {
138 	}
139 	void execute(string sql)
140 	{
141 		db.execute(sql);
142 	}
143 
144 	StatementHandle prepareStmt(string sql)
145 	{
146 		statements ~= db.prepare(sql);
147 		return statements.length - 1;
148 	}
149 
150 	ref Statement stmt(StatementHandle stmtHandle)
151 	{
152 		return statements[stmtHandle];
153 	}
154 }
155 
156 enum bool withoutRowid = true;
157 enum string withoutRowidStr = withoutRowid ? ` without rowid;` : ``;
158 
159 immutable perWorldTableCreate = `
160 create table if not exists per_world_data (
161   id text primary key,
162   data blob not null
163 )` ~ withoutRowidStr;
164 
165 immutable perWorldTableInsert = `insert or replace into per_world_data values (:id, :value)`;
166 immutable perWorldTableSelect = `select data from per_world_data where id = :id`;
167 immutable perWorldTableDelete = `delete from per_world_data where id = :id`;
168 
169 immutable perDimentionTableCreate = `
170 create table if not exists per_dimention_data(
171   id text,
172   dimention integer,
173   data blob not null,
174   primary key (id, dimention)
175 )` ~ withoutRowidStr;
176 
177 immutable perDimentionTableInsert =
178 `insert or replace into per_dimention_data values (:dim, :id, :value)`;
179 immutable perDimentionTableSelect = `
180 select data from per_dimention_data where dimention = :dim and id = :id`;
181 immutable perDimentionTableDelete = `
182 delete from per_dimention_data where dimention = :dim and id = :id`;
183 
184 immutable perChunkTableCreate = `
185 create table if not exists per_chunk_data(
186 	id integer primary key,
187 	data blob not null )`;
188 
189 immutable perChunkTableInsert = `insert or replace into per_chunk_data values (:id, :value)`;
190 immutable perChunkTableSelect = `select data from per_chunk_data where id = :id`;
191 immutable perChunkTableDelete = `delete from per_chunk_data where id = :id`;