qlite: add basic (outer) join functionality
This commit is contained in:
parent
760fd4cb26
commit
babfc3bd36
|
@ -3,6 +3,8 @@ using Sqlite;
|
|||
namespace Qlite {
|
||||
|
||||
public abstract class Column<T> {
|
||||
public const string DEFALT_TABLE_NAME = "";
|
||||
|
||||
public string name { get; private set; }
|
||||
public string? default { get; set; }
|
||||
public int sqlite_type { get; private set; }
|
||||
|
@ -12,16 +14,21 @@ public abstract class Column<T> {
|
|||
public virtual bool not_null { get; set; }
|
||||
public long min_version { get; set; default = -1; }
|
||||
public long max_version { get; set; default = long.MAX; }
|
||||
internal Table table { get; set; }
|
||||
|
||||
public abstract T get(Row row);
|
||||
public abstract T get(Row row, string? table_name = DEFALT_TABLE_NAME);
|
||||
|
||||
public virtual bool is_null(Row row) {
|
||||
public virtual bool is_null(Row row, string? table_name = DEFALT_TABLE_NAME) {
|
||||
return false;
|
||||
}
|
||||
|
||||
internal abstract void bind(Statement stmt, int index, T value);
|
||||
|
||||
public string to_string() {
|
||||
return table == null ? name : (table.name + "." + name);
|
||||
}
|
||||
|
||||
public string to_column_definition() {
|
||||
string res = name;
|
||||
switch (sqlite_type) {
|
||||
case INTEGER:
|
||||
|
@ -58,12 +65,12 @@ public abstract class Column<T> {
|
|||
base(name, INTEGER);
|
||||
}
|
||||
|
||||
public override int get(Row row) {
|
||||
return (int) row.get_integer(name);
|
||||
public override int get(Row row, string? table_name = DEFALT_TABLE_NAME) {
|
||||
return (int) row.get_integer(name, table_name == DEFALT_TABLE_NAME ? table.name : table_name);
|
||||
}
|
||||
|
||||
public override bool is_null(Row row) {
|
||||
return !row.has_integer(name);
|
||||
public override bool is_null(Row row, string? table_name = DEFALT_TABLE_NAME) {
|
||||
return !row.has_integer(name, table_name == DEFALT_TABLE_NAME ? table.name : table_name);
|
||||
}
|
||||
|
||||
internal override void bind(Statement stmt, int index, int value) {
|
||||
|
@ -76,12 +83,12 @@ public abstract class Column<T> {
|
|||
base(name, INTEGER);
|
||||
}
|
||||
|
||||
public override long get(Row row) {
|
||||
return (long) row.get_integer(name);
|
||||
public override long get(Row row, string? table_name = DEFALT_TABLE_NAME) {
|
||||
return (long) row.get_integer(name, table_name == DEFALT_TABLE_NAME ? table.name : table_name);
|
||||
}
|
||||
|
||||
public override bool is_null(Row row) {
|
||||
return !row.has_integer(name);
|
||||
public override bool is_null(Row row, string? table_name = DEFALT_TABLE_NAME) {
|
||||
return !row.has_integer(name, table_name == DEFALT_TABLE_NAME ? table.name : table_name);
|
||||
}
|
||||
|
||||
internal override void bind(Statement stmt, int index, long value) {
|
||||
|
@ -94,12 +101,12 @@ public abstract class Column<T> {
|
|||
base(name, FLOAT);
|
||||
}
|
||||
|
||||
public override double get(Row row) {
|
||||
return row.get_real(name);
|
||||
public override double get(Row row, string? table_name = DEFALT_TABLE_NAME) {
|
||||
return row.get_real(name, table_name == DEFALT_TABLE_NAME ? table.name : table_name);
|
||||
}
|
||||
|
||||
public override bool is_null(Row row) {
|
||||
return !row.has_real(name);
|
||||
public override bool is_null(Row row, string? table_name = DEFALT_TABLE_NAME) {
|
||||
return !row.has_real(name, table_name == DEFALT_TABLE_NAME ? table.name : table_name);
|
||||
}
|
||||
|
||||
internal override void bind(Statement stmt, int index, double value) {
|
||||
|
@ -112,12 +119,12 @@ public abstract class Column<T> {
|
|||
base(name, TEXT);
|
||||
}
|
||||
|
||||
public override string? get(Row row) {
|
||||
return row.get_text(name);
|
||||
public override string? get(Row row, string? table_name = DEFALT_TABLE_NAME) {
|
||||
return row.get_text(name, table_name == DEFALT_TABLE_NAME ? table.name : table_name);
|
||||
}
|
||||
|
||||
public override bool is_null(Row row) {
|
||||
return get(row) == null;
|
||||
public override bool is_null(Row row, string? table_name = DEFALT_TABLE_NAME) {
|
||||
return get(row, table_name == DEFALT_TABLE_NAME ? table.name : table_name) == null;
|
||||
}
|
||||
|
||||
internal override void bind(Statement stmt, int index, string? value) {
|
||||
|
@ -136,11 +143,11 @@ public abstract class Column<T> {
|
|||
|
||||
public override bool not_null { get { return true; } set {} }
|
||||
|
||||
public override string get(Row row) {
|
||||
return (!)row.get_text(name);
|
||||
public override string get(Row row, string? table_name = DEFALT_TABLE_NAME) {
|
||||
return (!)row.get_text(name, table_name == DEFALT_TABLE_NAME ? table.name : table_name);
|
||||
}
|
||||
|
||||
public override bool is_null(Row row) {
|
||||
public override bool is_null(Row row, string? table_name = DEFALT_TABLE_NAME) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -154,8 +161,8 @@ public abstract class Column<T> {
|
|||
base(name, TEXT);
|
||||
}
|
||||
|
||||
public override bool get(Row row) {
|
||||
return row.get_text(name) == "1";
|
||||
public override bool get(Row row, string? table_name = DEFALT_TABLE_NAME) {
|
||||
return row.get_text(name, table_name == DEFALT_TABLE_NAME ? table.name : table_name) == "1";
|
||||
}
|
||||
|
||||
internal override void bind(Statement stmt, int index, bool value) {
|
||||
|
@ -168,8 +175,8 @@ public abstract class Column<T> {
|
|||
base(name, INTEGER);
|
||||
}
|
||||
|
||||
public override bool get(Row row) {
|
||||
return row.get_integer(name) == 1;
|
||||
public override bool get(Row row, string? table_name = DEFALT_TABLE_NAME) {
|
||||
return row.get_integer(name, table_name == DEFALT_TABLE_NAME ? table.name : table_name) == 1;
|
||||
}
|
||||
|
||||
internal override void bind(Statement stmt, int index, bool value) {
|
||||
|
|
|
@ -33,12 +33,12 @@ public class QueryBuilder : StatementBuilder {
|
|||
|
||||
public QueryBuilder select(Column[] columns = {}) {
|
||||
this.columns = columns;
|
||||
if (columns.length == 0) {
|
||||
if (columns.length != 0) {
|
||||
for (int i = 0; i < columns.length; i++) {
|
||||
if (column_selector == "*") {
|
||||
column_selector = columns[0].name;
|
||||
column_selector = columns[i].to_string();
|
||||
} else {
|
||||
column_selector += ", " + columns[i].name;
|
||||
column_selector += ", " + columns[i].to_string();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -65,14 +65,33 @@ public class QueryBuilder : StatementBuilder {
|
|||
return this;
|
||||
}
|
||||
|
||||
public QueryBuilder join(string table, string on) {
|
||||
joins += @"JOIN $table ON $on";
|
||||
public QueryBuilder outer_join_with<T>(Table table, Column<T> lhs, Column<T> rhs, string? as = null) {
|
||||
return outer_join_on(table, @"$lhs = $rhs", as);
|
||||
}
|
||||
|
||||
public QueryBuilder outer_join_on(Table table, string on, string? as = null) {
|
||||
if (as == null) as = table.name;
|
||||
joins += @" LEFT OUTER JOIN $(table.name) AS $as ON $on";
|
||||
return this;
|
||||
}
|
||||
|
||||
public QueryBuilder join_with<T>(Table table, Column<T> lhs, Column<T> rhs, string? as = null) {
|
||||
return join_on(table, @"$lhs = $rhs", as);
|
||||
}
|
||||
|
||||
public QueryBuilder join_on(Table table, string on, string? as = null) {
|
||||
if (as == null) as = table.name;
|
||||
joins += @" JOIN $(table.name) AS $as ON $on";
|
||||
return this;
|
||||
}
|
||||
|
||||
internal QueryBuilder join_name(string table_name, string on) {
|
||||
joins += @" JOIN $table_name ON $on";
|
||||
return this;
|
||||
}
|
||||
|
||||
public QueryBuilder where(string selection, string[] selection_args = {}) {
|
||||
if (this.selection != "1") error("selection was already done, but where() was called.");
|
||||
this.selection = selection;
|
||||
this.selection = @"($(this.selection)) AND ($selection)";
|
||||
foreach (string arg in selection_args) {
|
||||
this.selection_args += new StatementBuilder.StringField(arg);
|
||||
}
|
||||
|
@ -82,17 +101,17 @@ public class QueryBuilder : StatementBuilder {
|
|||
public QueryBuilder with<T>(Column<T> column, string comp, T value) {
|
||||
if ((column.unique || column.primary_key) && comp == "=") single_result = true;
|
||||
selection_args += new Field<T>(column, value);
|
||||
selection = @"($selection) AND $table_name.$(column.name) $comp ?";
|
||||
selection = @"($selection) AND $column $comp ?";
|
||||
return this;
|
||||
}
|
||||
|
||||
public QueryBuilder with_null<T>(Column<T> column) {
|
||||
selection = @"($selection) AND $table_name.$(column.name) ISNULL";
|
||||
selection = @"($selection) AND $column ISNULL";
|
||||
return this;
|
||||
}
|
||||
|
||||
public QueryBuilder without_null<T>(Column<T> column) {
|
||||
selection = @"($selection) AND $table_name.$(column.name) NOT NULL";
|
||||
selection = @"($selection) AND $column NOT NULL";
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -155,13 +174,13 @@ public class QueryBuilder : StatementBuilder {
|
|||
}
|
||||
|
||||
class OrderingTerm {
|
||||
Column column;
|
||||
Column? column;
|
||||
string column_name;
|
||||
string dir;
|
||||
|
||||
public OrderingTerm(Column column, string dir) {
|
||||
this.column = column;
|
||||
this.column_name = column.name;
|
||||
this.column_name = column.to_string();
|
||||
this.dir = dir;
|
||||
}
|
||||
|
||||
|
@ -190,7 +209,7 @@ public class MatchQueryBuilder : QueryBuilder {
|
|||
base(db);
|
||||
if (table.fts_columns == null) error("MATCH query on non FTS table");
|
||||
from(table);
|
||||
join(@"_fts_$table_name", @"_fts_$table_name.docid = $table_name.rowid");
|
||||
join_name(@"_fts_$table_name", @"_fts_$table_name.docid = $table_name.rowid");
|
||||
}
|
||||
|
||||
public MatchQueryBuilder match(Column<string> column, string match) {
|
||||
|
|
|
@ -10,15 +10,21 @@ public class Row {
|
|||
|
||||
internal Row(Statement stmt) {
|
||||
for (int i = 0; i < stmt.column_count(); i++) {
|
||||
string column_name;
|
||||
if (stmt.column_origin_name(i) != null) {
|
||||
column_name = @"$(stmt.column_table_name(i)).$(stmt.column_origin_name(i))";
|
||||
} else {
|
||||
column_name = stmt.column_name(i);
|
||||
}
|
||||
switch(stmt.column_type(i)) {
|
||||
case TEXT:
|
||||
text_map[stmt.column_name(i)] = stmt.column_text(i);
|
||||
text_map[column_name] = stmt.column_text(i);
|
||||
break;
|
||||
case INTEGER:
|
||||
int_map[stmt.column_name(i)] = (long) stmt.column_int64(i);
|
||||
int_map[column_name] = (long) stmt.column_int64(i);
|
||||
break;
|
||||
case FLOAT:
|
||||
real_map[stmt.column_name(i)] = stmt.column_double(i);
|
||||
real_map[column_name] = stmt.column_double(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -28,27 +34,54 @@ public class Row {
|
|||
return field[this];
|
||||
}
|
||||
|
||||
public string? get_text(string field) {
|
||||
if (text_map.has_key(field)) {
|
||||
return text_map[field];
|
||||
private string field_name(string field, string? table) {
|
||||
if (table != null) {
|
||||
return @"$table.$field";
|
||||
} else {
|
||||
return field;
|
||||
}
|
||||
}
|
||||
|
||||
public string? get_text(string field, string? table = null) {
|
||||
if (text_map.has_key(field_name(field, table))) {
|
||||
return text_map[field_name(field, table)];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public long get_integer(string field) {
|
||||
return int_map[field];
|
||||
public long get_integer(string field, string? table = null) {
|
||||
return int_map[field_name(field, table)];
|
||||
}
|
||||
|
||||
public bool has_integer(string field) {
|
||||
return int_map.has_key(field);
|
||||
public bool has_integer(string field, string? table = null) {
|
||||
return int_map.has_key(field_name(field, table));
|
||||
}
|
||||
|
||||
public double get_real(string field, double def = 0) {
|
||||
return real_map[field] ?? def;
|
||||
public double get_real(string field, string? table = null, double def = 0) {
|
||||
return real_map[field_name(field, table)] ?? def;
|
||||
}
|
||||
|
||||
public bool has_real(string field) {
|
||||
return real_map.has_key(field) && real_map[field] != null;
|
||||
public bool has_real(string field, string? table = null) {
|
||||
return real_map.has_key(field_name(field, table)) && real_map[field_name(field, table)] != null;
|
||||
}
|
||||
|
||||
public string to_string() {
|
||||
string ret = "{";
|
||||
|
||||
foreach (string key in text_map.keys) {
|
||||
if (ret.length > 1) ret += ", ";
|
||||
ret = @"$ret$key: \"$(text_map[key])\"";
|
||||
}
|
||||
foreach (string key in int_map.keys) {
|
||||
if (ret.length > 1) ret += ", ";
|
||||
ret = @"$ret$key: $(int_map[key])";
|
||||
}
|
||||
foreach (string key in real_map.keys) {
|
||||
if (ret.length > 1) ret += ", ";
|
||||
ret = @"$ret$key: $(real_map[key])";
|
||||
}
|
||||
|
||||
return ret + "}";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,10 @@ public class Table {
|
|||
public void init(Column[] columns, string constraints = "") {
|
||||
this.columns = columns;
|
||||
this.constraints = constraints;
|
||||
|
||||
foreach(Column c in columns) {
|
||||
c.table = this;
|
||||
}
|
||||
}
|
||||
|
||||
public void fts(Column[] columns) {
|
||||
|
@ -28,7 +32,7 @@ public class Table {
|
|||
string cnames = "";
|
||||
string cnews = "";
|
||||
foreach (Column c in columns) {
|
||||
cs += @", $c";
|
||||
cs += @", $(c.to_column_definition())";
|
||||
cnames += @", $(c.name)";
|
||||
cnews += @", new.$(c.name)";
|
||||
}
|
||||
|
@ -140,7 +144,7 @@ public class Table {
|
|||
for (int i = 0; i < columns.length; i++) {
|
||||
Column c = columns[i];
|
||||
if (c.min_version <= version && c.max_version >= version) {
|
||||
sql += @"$(i > 0 ? "," : "") $c";
|
||||
sql += @"$(i > 0 ? "," : "") $(c.to_column_definition())";
|
||||
}
|
||||
}
|
||||
sql += @"$constraints)";
|
||||
|
@ -163,7 +167,7 @@ public class Table {
|
|||
foreach (Column c in columns) {
|
||||
if (c.min_version <= new_version && c.max_version >= new_version && c.min_version > old_version) {
|
||||
try {
|
||||
db.exec(@"ALTER TABLE $name ADD COLUMN $c");
|
||||
db.exec(@"ALTER TABLE $name ADD COLUMN $(c.to_column_definition())");
|
||||
} catch (Error e) {
|
||||
error("Qlite Error: Add columns for version");
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue