Skip to content

Instantly share code, notes, and snippets.

@wmanley
Created February 6, 2013 09:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wmanley/4721469 to your computer and use it in GitHub Desktop.
Save wmanley/4721469 to your computer and use it in GitHub Desktop.
A code sketch of generated C++ code from SQLite SQL with the aim of being able to edit the SQL with a generated program to check ABI compatibility.

Goals:

  • Minimal dependencies
  • Complementary to sqlite C API, rather than a wrapper for it
  • Provides type safety
  • SQL must be modifiable later
  • It should be easy to check that ABI hasn't broken

DBus XML SQL Serialisation thingy

  • Want to increase flexibility by letting us modify the SQL post compilation
    • Such that we can apply optimisations that would be hard to express in code SQL thingy.

    • Even so:

      a.x = b.x;
      a.y = b.y;
      a.z = transform(b.z);
      

      is pretty error prone and ugly for the common case where the majority of things have no additional explicit transformations beyond C++ assignment and implicit conversions.

      The purely implicit case is limited to changes on a type by type case rather than a member by member one.

      Would be most neat to put those transformations in the SQL itself, but that would be mixing dynamic and static data/code and would work against flexibility.

      Can we register functions with SQLite for transformations? How will that work with type conversions?

struct Blob {
size_t len;
char* data;
};
INTEGER -> int64_t;
REAL -> double;
TEXT -> char*;
// BLOB -> Blob
class Statement {
public:
/**
* Construct a query
*/
Query(SQLite* db, const char* path);
static const int param_count = {{PARAM_COUNT}};
static const char* params = {"p0", "p1", "p2"};
static const char* param_types = { INTEGER, FLOAT, TEXT, BLOB, NULL }
static const int column_count = {{COLUMN_COUNT}};
static const char* columns = {"col0", "col1", "col2"};
static const char* column_types = { INTEGER, FLOAT, TEXT, BLOB, NULL };
int param_pos[param_count];
int column_pos[column_count];
static Query create(const char* query) {
if (!check(query)) {
throw XYZ;
}
for (int i=0; i<N; ++i) {
int pos[i] = sqlite3_bind_parameter_index(pStmt, params[i]);
if (pos[i] == 0) {
// Error!!!
}
// Also check that all parameters have been bound at this point
}
if (sqlite3_column_count(pStmt) != column_count) {
// Error!!!
}
// NOTE: SQLite is dynamically typed so this will not work in all
// cases:
for (int i=0; i<column_count; ++i) {
// query_type may be NULL if the column is an expression as SQLite
// is dynamically typed. See `sqlite3_column_decltype` in the
// SQLite documentation for more information.
const char* query_type = sqlite3_column_decltype(pStmt, i);
if (query_type && strcmp(query_type, declared_types[i]) != 0) {
// Error types don't match
}
}
// Match up column number with name
for (int i=0; i<column_count; ++i) {
column_pos[i] = -1;
}
for (int i=0; i<column_count; ++i) {
const char* name = sqlite3_column_name(sqlite3_stmt* pStmt, i);
for (int j=0; j<column_count; ++j) {
int matched = 0;
if (strcmp(name, columns[j]) == 0) {
if (column_pos[j] == -1) {
matched = 1;
column_pos[j] = i;
break;
}
else {
// Error: we've already assigned to this column
}
}
if (!matched) {
// Error: Don't have a column called this
}
}
}
}
unsigned char* append_string_to_result(unsigned char** extra_data, int column) {
unsigned char* out = *extra_data;
const unsigned char* text = sqlite3_column_text(pStmt, column);
unsigned bytes = sqlite3_column_bytes(pStmt, column);
memcpy(out, text, bytes);
*extra_data += bytes;
return out;
}
/**
* The data returned from this function must be freed with sqlite_free
*/
Result* step() {
// Work out how much additional space will be required
int extra_bytes = 0;
for (int i = 0; i < column_count; ++i) {
if (column_types[i] = SQLITE_TEXT) {
sqlite3_column_text(pStmt, column_pos[3]);
int size = sqlite3_column_bytes(pStmt, column_pos[i]);
extra_bytes += size;
}
}
void* data = sqlite_malloc(sizeof(Result) + extra_bytes);
if (!data) {
errno = ENOMEM;
goto fail;
}
unsigned char* extra_data = (unsigned char*) data + extra_bytes;
Result* result = (Result*) result;
result->col0 = sqlite3_column_double(pStmt, column_pos[0]);
result->col1 = sqlite3_column_int64(pStmt, column_pos[1]);
result->col2 = sqlite3_column_int(pStmt, column_pos[2]);
result->col3 = append_string_to_result(&extra_data, column_pos[3]);
return result;
fail:
sqlite_free(result);
}
void bind(T0 name0, T1 name1, T2 name2) {
sqlite_bind_T0(pStmt, pos[0], name0);
sqlite_bind_T1(pStmt, pos[1], name1);
sqlite_bind_T2(pStmt, pos[2], name2);
sqlite_bind_T3(pStmt, pos[3], name3);
sqlite_bind_blob(pStmt, pos[0],
sqlite_bind_double
sqlite_bind_int64
sqlite_bind_text
}
void check
void clear_bindings() {
sqlite3_clear_bindings(pStmt);
}
void reset() {
sqlite3_reset(pStmt);
}
~Query() {
sqlite3_finalize(pStmt);
}
private:
sqlite3_stmt* pStmt;
};
Statement create(db, path) {
sqlite3_stmt* pStmt;
string sql = read_contents(path);
sqlite_prepare_v2(db, sql.c_str(), sql.length(), pStmt, NULL);
int sqlite3_prepare_v2(
sqlite3 *db, /* Database handle */
const char *zSql, /* SQL statement, UTF-8 encoded */
int nByte, /* Maximum length of zSql in bytes. */
sqlite3_stmt **ppStmt, /* OUT: Statement handle */
const char **pzTail /* OUT: Pointer to unused portion of zSql */
);
struct Result {
const char* x;
int64_t y;
};
ResultIterator {
std::auto_ptr<Result> operator*();
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment