1 #include <stdexcept>
2 #include <unistd.h> // for usleep
3 #include <iostream>
4 #include <algorithm>
5 #include <time.h>
6 #include "db-sqlite3.h"
7 #include "types.h"
8 
9 #define SQLRES(f, good) \
10 	result = (sqlite3_##f);\
11 	if (result != good) {\
12 		throw std::runtime_error(sqlite3_errmsg(db));\
13 	}
14 #define SQLOK(f) SQLRES(f, SQLITE_OK)
15 
DBSQLite3(const std::string & mapdir)16 DBSQLite3::DBSQLite3(const std::string &mapdir)
17 {
18 	int result;
19 	std::string db_name = mapdir + "map.sqlite";
20 
21 	SQLOK(open_v2(db_name.c_str(), &db, SQLITE_OPEN_READONLY |
22 			SQLITE_OPEN_PRIVATECACHE, 0))
23 
24 	SQLOK(prepare_v2(db,
25 			"SELECT pos, data FROM blocks WHERE pos BETWEEN ? AND ?",
26 		-1, &stmt_get_blocks_z, NULL))
27 
28 	SQLOK(prepare_v2(db,
29 			"SELECT data FROM blocks WHERE pos = ?",
30 		-1, &stmt_get_block_exact, NULL))
31 
32 	SQLOK(prepare_v2(db,
33 			"SELECT pos FROM blocks",
34 		-1, &stmt_get_block_pos, NULL))
35 
36 	SQLOK(prepare_v2(db,
37 			"SELECT pos FROM blocks WHERE pos BETWEEN ? AND ?",
38 		-1, &stmt_get_block_pos_z, NULL))
39 }
40 
41 
~DBSQLite3()42 DBSQLite3::~DBSQLite3()
43 {
44 	sqlite3_finalize(stmt_get_blocks_z);
45 	sqlite3_finalize(stmt_get_block_pos);
46 	sqlite3_finalize(stmt_get_block_pos_z);
47 	sqlite3_finalize(stmt_get_block_exact);
48 
49 	if (sqlite3_close(db) != SQLITE_OK) {
50 		std::cerr << "Error closing SQLite database." << std::endl;
51 	};
52 }
53 
54 
getPosRange(int64_t & min,int64_t & max,int16_t zPos,int16_t zPos2) const55 inline void DBSQLite3::getPosRange(int64_t &min, int64_t &max, int16_t zPos,
56 		int16_t zPos2) const
57 {
58 	/* The range of block positions is [-2048, 2047], which turns into [0, 4095]
59 	 * when casted to unsigned. This didn't actually help me understand the
60 	 * numbers below, but I wanted to write it down.
61 	 */
62 
63 	// Magic numbers!
64 	min = encodeBlockPos(BlockPos(0, -2048, zPos));
65 	max = encodeBlockPos(BlockPos(0, 2048, zPos2)) - 1;
66 }
67 
68 
getBlockPos(BlockPos min,BlockPos max)69 std::vector<BlockPos> DBSQLite3::getBlockPos(BlockPos min, BlockPos max)
70 {
71 	int result;
72 	sqlite3_stmt *stmt;
73 
74 	if(min.z <= -2048 && max.z >= 2048) {
75 		stmt = stmt_get_block_pos;
76 	} else {
77 		stmt = stmt_get_block_pos_z;
78 		int64_t minPos, maxPos;
79 		if (min.z < -2048)
80 			min.z = -2048;
81 		if (max.z > 2048)
82 			max.z = 2048;
83 		getPosRange(minPos, maxPos, min.z, max.z - 1);
84 		SQLOK(bind_int64(stmt, 1, minPos))
85 		SQLOK(bind_int64(stmt, 2, maxPos))
86 	}
87 
88 	std::vector<BlockPos> positions;
89 	while ((result = sqlite3_step(stmt)) != SQLITE_DONE) {
90 		if (result == SQLITE_BUSY) { // Wait some time and try again
91 			usleep(10000);
92 		} else if (result != SQLITE_ROW) {
93 			throw std::runtime_error(sqlite3_errmsg(db));
94 		}
95 
96 		int64_t posHash = sqlite3_column_int64(stmt, 0);
97 		BlockPos pos = decodeBlockPos(posHash);
98 		if(pos.x >= min.x && pos.x < max.x && pos.y >= min.y && pos.y < max.y)
99 			positions.emplace_back(pos);
100 	}
101 	SQLOK(reset(stmt));
102 	return positions;
103 }
104 
105 
loadBlockCache(int16_t zPos)106 void DBSQLite3::loadBlockCache(int16_t zPos)
107 {
108 	int result;
109 	blockCache.clear();
110 
111 	int64_t minPos, maxPos;
112 	getPosRange(minPos, maxPos, zPos, zPos);
113 
114 	SQLOK(bind_int64(stmt_get_blocks_z, 1, minPos));
115 	SQLOK(bind_int64(stmt_get_blocks_z, 2, maxPos));
116 
117 	while ((result = sqlite3_step(stmt_get_blocks_z)) != SQLITE_DONE) {
118 		if (result == SQLITE_BUSY) { // Wait some time and try again
119 			usleep(10000);
120 		} else if (result != SQLITE_ROW) {
121 			throw std::runtime_error(sqlite3_errmsg(db));
122 		}
123 
124 		int64_t posHash = sqlite3_column_int64(stmt_get_blocks_z, 0);
125 		BlockPos pos = decodeBlockPos(posHash);
126 		const unsigned char *data = reinterpret_cast<const unsigned char *>(
127 				sqlite3_column_blob(stmt_get_blocks_z, 1));
128 		size_t size = sqlite3_column_bytes(stmt_get_blocks_z, 1);
129 		blockCache[pos.x].emplace_back(pos, ustring(data, size));
130 	}
131 	SQLOK(reset(stmt_get_blocks_z))
132 }
133 
134 
getBlocksOnXZ(BlockList & blocks,int16_t x,int16_t z,int16_t min_y,int16_t max_y)135 void DBSQLite3::getBlocksOnXZ(BlockList &blocks, int16_t x, int16_t z,
136 		int16_t min_y, int16_t max_y)
137 {
138 	/* Cache the blocks on the given Z coordinate between calls, this only
139 	 * works due to order in which the TileGenerator asks for blocks. */
140 	if (z != blockCachedZ) {
141 		loadBlockCache(z);
142 		blockCachedZ = z;
143 	}
144 
145 	auto it = blockCache.find(x);
146 	if (it == blockCache.end())
147 		return;
148 
149 	if (it->second.empty()) {
150 		/* We have swapped this list before, this is not supposed to happen
151 		 * because it's bad for performance. But rather than silently breaking
152 		 * do the right thing and load the blocks again. */
153 #ifndef NDEBUG
154 		std::cout << "Warning: suboptimal access pattern for sqlite3 backend" << std::endl;
155 #endif
156 		loadBlockCache(z);
157 	}
158 	// Swap lists to avoid copying contents
159 	blocks.clear();
160 	std::swap(blocks, it->second);
161 
162 	for (auto it = blocks.begin(); it != blocks.end(); ) {
163 		if (it->first.y < min_y || it->first.y >= max_y)
164 			it = blocks.erase(it);
165 		else
166 			it++;
167 	}
168 }
169 
170 
getBlocksByPos(BlockList & blocks,const std::vector<BlockPos> & positions)171 void DBSQLite3::getBlocksByPos(BlockList &blocks,
172 			const std::vector<BlockPos> &positions)
173 {
174 	int result;
175 
176 	for (auto pos : positions) {
177 		int64_t dbPos = encodeBlockPos(pos);
178 		SQLOK(bind_int64(stmt_get_block_exact, 1, dbPos));
179 
180 		while ((result = sqlite3_step(stmt_get_block_exact)) == SQLITE_BUSY) {
181 			usleep(10000); // Wait some time and try again
182 		}
183 		if (result == SQLITE_DONE) {
184 			// no data
185 		} else if (result != SQLITE_ROW) {
186 			throw std::runtime_error(sqlite3_errmsg(db));
187 		} else {
188 			const unsigned char *data = reinterpret_cast<const unsigned char *>(
189 					sqlite3_column_blob(stmt_get_block_exact, 0));
190 			size_t size = sqlite3_column_bytes(stmt_get_block_exact, 0);
191 			blocks.emplace_back(pos, ustring(data, size));
192 		}
193 
194 		SQLOK(reset(stmt_get_block_exact))
195 	}
196 }
197