Skip to content

Commit ae11e27

Browse files
authored
Merge pull request #34 from Mytherin/norowid
Allow scanning of tables without a row id, and report correct error code when calling sqlite_attach on a non-existent database
2 parents a20336d + 4c6d172 commit ae11e27

File tree

6 files changed

+53
-11
lines changed

6 files changed

+53
-11
lines changed

data/norowid.db

8 KB
Binary file not shown.

src/include/sqlite_db.hpp

+3-1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ class SQLiteDB {
2929

3030
public:
3131
static SQLiteDB Open(const string &path, bool is_read_only = true, bool is_shared = false);
32+
bool TryPrepare(const string &query, SQLiteStatement &result);
3233
SQLiteStatement Prepare(const string &query);
3334
void Execute(const string &query);
3435
vector<string> GetTables();
@@ -40,7 +41,8 @@ class SQLiteDB {
4041
void GetViewInfo(const string &view_name, string &sql);
4142
void GetIndexInfo(const string &index_name, string &sql, string &table_name);
4243
idx_t RunPragma(string pragma_name);
43-
idx_t GetMaxRowId(const string &table_name);
44+
//! Gets the max row id of a table, returns false if the table does not have a rowid column
45+
bool GetMaxRowId(const string &table_name, idx_t &row_id);
4446
bool ColumnExists(const string &table_name, const string &column_name);
4547

4648
bool IsOpen();

src/sqlite_db.cpp

+22-7
Original file line numberDiff line numberDiff line change
@@ -39,15 +39,26 @@ SQLiteDB SQLiteDB::Open(const string &path, bool is_read_only, bool is_shared) {
3939
// FIXME: we should just make sure we are not re-using the same `sqlite3` object across threads
4040
flags |= SQLITE_OPEN_NOMUTEX;
4141
}
42-
SQLiteUtils::Check(sqlite3_open_v2(path.c_str(), &result.db, flags, nullptr), result.db);
42+
flags |= SQLITE_OPEN_EXRESCODE;
43+
auto rc = sqlite3_open_v2(path.c_str(), &result.db, flags, nullptr);
44+
if (rc != SQLITE_OK) {
45+
throw std::runtime_error("Unable to open database \"" + path + "\": " + string(sqlite3_errstr(rc)));
46+
}
4347
return result;
4448
}
4549

46-
SQLiteStatement SQLiteDB::Prepare(const string &query) {
47-
SQLiteStatement stmt;
50+
bool SQLiteDB::TryPrepare(const string &query, SQLiteStatement &stmt) {
4851
stmt.db = db;
4952
auto rc = sqlite3_prepare_v2(db, query.c_str(), -1, &stmt.stmt, nullptr);
5053
if (rc != SQLITE_OK) {
54+
return false;
55+
}
56+
return true;
57+
}
58+
59+
SQLiteStatement SQLiteDB::Prepare(const string &query) {
60+
SQLiteStatement stmt;
61+
if (!TryPrepare(query, stmt)) {
5162
string error = "Failed to prepare query \"" + query + "\": " + string(sqlite3_errmsg(db));
5263
throw std::runtime_error(error);
5364
}
@@ -196,13 +207,17 @@ bool SQLiteDB::ColumnExists(const string &table_name, const string &column_name)
196207
return false;
197208
}
198209

199-
idx_t SQLiteDB::GetMaxRowId(const string &table_name) {
210+
bool SQLiteDB::GetMaxRowId(const string &table_name, idx_t &max_row_id) {
200211
SQLiteStatement stmt;
201-
stmt = Prepare(StringUtil::Format("SELECT MAX(ROWID) FROM \"%s\"", SQLiteUtils::SanitizeIdentifier(table_name)));
212+
if (!TryPrepare(StringUtil::Format("SELECT MAX(ROWID) FROM \"%s\"", SQLiteUtils::SanitizeIdentifier(table_name)),
213+
stmt)) {
214+
return false;
215+
}
202216
if (!stmt.Step()) {
203-
throw std::runtime_error("could not find max rowid?");
217+
return false;
204218
}
205-
return stmt.GetValue<int64_t>(0);
219+
max_row_id = stmt.GetValue<int64_t>(0);
220+
return true;
206221
}
207222

208223
idx_t SQLiteDB::RunPragma(string pragma_name) {

src/sqlite_scanner.cpp

+4-1
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,10 @@ static unique_ptr<FunctionData> SqliteBind(ClientContext &context, TableFunction
7070
throw std::runtime_error("no columns for table " + result->table_name);
7171
}
7272

73-
result->max_rowid = db.GetMaxRowId(result->table_name);
73+
if (!db.GetMaxRowId(result->table_name, result->max_rowid)) {
74+
result->max_rowid = idx_t(-1);
75+
result->rows_per_group = idx_t(-1);
76+
}
7477

7578
result->names = names;
7679
result->types = return_types;

src/storage/sqlite_table_entry.cpp

+8-2
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,10 @@ TableFunction SQLiteTableEntry::GetScanFunction(ClientContext &context, unique_p
2828
auto &transaction = (SQLiteTransaction &)Transaction::Get(context, *catalog);
2929
auto &db = transaction.GetDB();
3030

31-
result->max_rowid = db.GetMaxRowId(name);
31+
if (!db.GetMaxRowId(name, result->max_rowid)) {
32+
result->max_rowid = idx_t(-1);
33+
result->rows_per_group = idx_t(-1);
34+
}
3235
if (!transaction.IsReadOnly() || sqlite_catalog->InMemory()) {
3336
// for in-memory databases or if we have transaction-local changes we can only do a single-threaded scan
3437
// set up the transaction's connection object as the global db
@@ -44,7 +47,10 @@ TableStorageInfo SQLiteTableEntry::GetStorageInfo(ClientContext &context) {
4447
auto &transaction = (SQLiteTransaction &)Transaction::Get(context, *catalog);
4548
auto &db = transaction.GetDB();
4649
TableStorageInfo result;
47-
result.cardinality = db.GetMaxRowId(name);
50+
if (!db.GetMaxRowId(name, result.cardinality)) {
51+
// probably
52+
result.cardinality = 10000;
53+
}
4854
return result;
4955
}
5056

test/lite_storage/attach_norowid.test

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
statement ok
2+
LOAD 'build/release/extension/sqlite_scanner/sqlite_scanner.duckdb_extension';
3+
4+
statement ok
5+
ATTACH 'data/norowid.db' AS s (TYPE SQLITE, READ_ONLY)
6+
7+
query II
8+
SELECT * FROM s.wordcount
9+
----
10+
hello 10
11+
world 5
12+
13+
query II
14+
SELECT * FROM s.wordcount WHERE word='world'
15+
----
16+
world 5

0 commit comments

Comments
 (0)