2017-03-02 14:37:32 +00:00
|
|
|
using Sqlite;
|
|
|
|
|
|
|
|
namespace Qlite {
|
|
|
|
|
|
|
|
public class QueryBuilder : StatementBuilder {
|
|
|
|
private bool single_result;
|
|
|
|
|
|
|
|
// SELECT [...]
|
|
|
|
private string column_selector = "*";
|
2017-04-16 13:11:00 +00:00
|
|
|
private Column[] columns = {};
|
2017-03-02 14:37:32 +00:00
|
|
|
|
|
|
|
// FROM [...]
|
2018-06-17 23:47:43 +00:00
|
|
|
protected Table? table;
|
|
|
|
protected string? table_name;
|
|
|
|
|
|
|
|
// JOIN [...]
|
|
|
|
private string joins = "";
|
2017-03-02 14:37:32 +00:00
|
|
|
|
|
|
|
// WHERE [...]
|
2018-06-17 23:47:43 +00:00
|
|
|
protected string selection = "1";
|
|
|
|
internal StatementBuilder.AbstractField[] selection_args = {};
|
2017-03-02 14:37:32 +00:00
|
|
|
|
|
|
|
// ORDER BY [...]
|
2017-04-16 13:11:00 +00:00
|
|
|
private OrderingTerm[]? order_by_terms = {};
|
2017-03-02 14:37:32 +00:00
|
|
|
|
2018-07-12 18:27:50 +00:00
|
|
|
// LIMIT [...] OFFSET [...]
|
2017-03-02 14:37:32 +00:00
|
|
|
private int limit_val;
|
2018-07-12 18:27:50 +00:00
|
|
|
private int offset_val;
|
2017-03-02 14:37:32 +00:00
|
|
|
|
2017-03-20 18:27:39 +00:00
|
|
|
internal QueryBuilder(Database db) {
|
2017-03-02 14:37:32 +00:00
|
|
|
base(db);
|
|
|
|
}
|
|
|
|
|
2017-04-16 13:11:00 +00:00
|
|
|
public QueryBuilder select(Column[] columns = {}) {
|
2017-03-02 14:37:32 +00:00
|
|
|
this.columns = columns;
|
2018-06-27 14:58:10 +00:00
|
|
|
if (columns.length != 0) {
|
2017-03-02 14:37:32 +00:00
|
|
|
for (int i = 0; i < columns.length; i++) {
|
|
|
|
if (column_selector == "*") {
|
2018-06-27 14:58:10 +00:00
|
|
|
column_selector = columns[i].to_string();
|
2017-03-02 14:37:32 +00:00
|
|
|
} else {
|
2018-06-27 14:58:10 +00:00
|
|
|
column_selector += ", " + columns[i].to_string();
|
2017-03-02 14:37:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
column_selector = "*";
|
|
|
|
}
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public QueryBuilder select_string(string column_selector) {
|
2017-04-16 13:11:00 +00:00
|
|
|
this.columns = {};
|
2017-03-02 14:37:32 +00:00
|
|
|
this.column_selector = column_selector;
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
2018-06-17 23:47:43 +00:00
|
|
|
public virtual QueryBuilder from(Table table) {
|
2017-10-28 21:48:07 +00:00
|
|
|
if (this.table_name != null) error("cannot use from() multiple times.");
|
2017-03-02 14:37:32 +00:00
|
|
|
this.table = table;
|
|
|
|
this.table_name = table.name;
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
2018-06-17 23:47:43 +00:00
|
|
|
public virtual QueryBuilder from_name(string table) {
|
2017-03-02 14:37:32 +00:00
|
|
|
this.table_name = table;
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
2018-06-27 14:58:10 +00:00
|
|
|
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";
|
2018-06-17 23:47:43 +00:00
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
2017-10-28 21:48:07 +00:00
|
|
|
public QueryBuilder where(string selection, string[] selection_args = {}) {
|
2018-06-27 14:58:10 +00:00
|
|
|
this.selection = @"($(this.selection)) AND ($selection)";
|
2017-04-16 13:11:00 +00:00
|
|
|
foreach (string arg in selection_args) {
|
|
|
|
this.selection_args += new StatementBuilder.StringField(arg);
|
2017-03-02 14:37:32 +00:00
|
|
|
}
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public QueryBuilder with<T>(Column<T> column, string comp, T value) {
|
|
|
|
if ((column.unique || column.primary_key) && comp == "=") single_result = true;
|
2017-04-16 13:11:00 +00:00
|
|
|
selection_args += new Field<T>(column, value);
|
2018-06-27 14:58:10 +00:00
|
|
|
selection = @"($selection) AND $column $comp ?";
|
2017-03-02 14:37:32 +00:00
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public QueryBuilder with_null<T>(Column<T> column) {
|
2018-06-27 14:58:10 +00:00
|
|
|
selection = @"($selection) AND $column ISNULL";
|
2017-03-02 14:37:32 +00:00
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public QueryBuilder without_null<T>(Column<T> column) {
|
2018-06-27 14:58:10 +00:00
|
|
|
selection = @"($selection) AND $column NOT NULL";
|
2017-03-02 14:37:32 +00:00
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public QueryBuilder order_by(Column column, string dir = "ASC") {
|
2017-04-16 13:11:00 +00:00
|
|
|
order_by_terms += new OrderingTerm(column, dir);
|
2017-03-02 14:37:32 +00:00
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public QueryBuilder order_by_name(string name, string dir) {
|
2017-04-16 13:11:00 +00:00
|
|
|
order_by_terms += new OrderingTerm.by_name(name, dir);
|
2017-03-02 14:37:32 +00:00
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public QueryBuilder limit(int limit) {
|
2018-06-19 10:52:00 +00:00
|
|
|
if (this.limit_val != 0 && limit > this.limit_val) error("tried to increase an existing limit");
|
2017-03-02 14:37:32 +00:00
|
|
|
this.limit_val = limit;
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
2018-07-12 18:27:50 +00:00
|
|
|
public QueryBuilder offset(int offset) {
|
|
|
|
if (this.limit_val == 0) error("limit required before offset");
|
|
|
|
this.offset_val = offset;
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
2018-06-19 10:52:00 +00:00
|
|
|
public QueryBuilder single() {
|
|
|
|
this.single_result = true;
|
|
|
|
return limit(1);
|
|
|
|
}
|
|
|
|
|
2017-10-28 21:48:07 +00:00
|
|
|
public int64 count() {
|
2017-03-02 14:37:32 +00:00
|
|
|
this.column_selector = @"COUNT($column_selector) AS count";
|
|
|
|
this.single_result = true;
|
2017-04-16 13:11:00 +00:00
|
|
|
return row().get_integer("count");
|
2017-03-02 14:37:32 +00:00
|
|
|
}
|
|
|
|
|
2017-10-28 21:48:07 +00:00
|
|
|
private Row? row_() {
|
|
|
|
if (!single_result) error("query is not suited to return a single row, but row() was called.");
|
2017-04-16 13:11:00 +00:00
|
|
|
return iterator().get_next();
|
2017-03-02 14:37:32 +00:00
|
|
|
}
|
|
|
|
|
2017-10-28 21:48:07 +00:00
|
|
|
public RowOption row() {
|
2017-03-12 18:33:31 +00:00
|
|
|
return new RowOption(row_());
|
|
|
|
}
|
|
|
|
|
2018-06-19 10:52:00 +00:00
|
|
|
public T get<T>(Column<T> field, T def = null) {
|
|
|
|
return row().get(field, def);
|
2017-03-02 14:37:32 +00:00
|
|
|
}
|
|
|
|
|
2017-10-28 21:48:07 +00:00
|
|
|
internal override Statement prepare() {
|
2018-06-17 23:47:43 +00:00
|
|
|
Statement stmt = db.prepare(@"SELECT $column_selector $(table_name == null ? "" : @"FROM $((!) table_name)") $joins WHERE $selection $(OrderingTerm.all_to_string(order_by_terms)) $(limit_val > 0 ? @" LIMIT $limit_val OFFSET $offset_val" : "")");
|
2017-03-02 14:37:32 +00:00
|
|
|
for (int i = 0; i < selection_args.length; i++) {
|
|
|
|
selection_args[i].bind(stmt, i+1);
|
|
|
|
}
|
|
|
|
return stmt;
|
|
|
|
}
|
|
|
|
|
2017-10-28 21:48:07 +00:00
|
|
|
public RowIterator iterator() {
|
2017-03-22 22:55:19 +00:00
|
|
|
return new RowIterator.from_query_builder(db, this);
|
2017-03-02 14:37:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
class OrderingTerm {
|
2018-06-27 14:58:10 +00:00
|
|
|
Column? column;
|
2017-03-02 14:37:32 +00:00
|
|
|
string column_name;
|
|
|
|
string dir;
|
|
|
|
|
|
|
|
public OrderingTerm(Column column, string dir) {
|
|
|
|
this.column = column;
|
2018-06-27 14:58:10 +00:00
|
|
|
this.column_name = column.to_string();
|
2017-03-02 14:37:32 +00:00
|
|
|
this.dir = dir;
|
|
|
|
}
|
|
|
|
|
|
|
|
public OrderingTerm.by_name(string column_name, string dir) {
|
|
|
|
this.column_name = column_name;
|
|
|
|
this.dir = dir;
|
|
|
|
}
|
|
|
|
|
|
|
|
public string to_string() {
|
|
|
|
return @"$column_name $dir";
|
|
|
|
}
|
|
|
|
|
2017-04-16 13:11:00 +00:00
|
|
|
public static string all_to_string(OrderingTerm[]? terms) {
|
|
|
|
if (terms == null || terms.length == 0) return "";
|
2017-03-02 14:37:32 +00:00
|
|
|
string res = "ORDER BY "+terms[0].to_string();
|
|
|
|
for (int i = 1; i < terms.length; i++) {
|
|
|
|
res += @", $(terms[i])";
|
|
|
|
}
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-17 23:47:43 +00:00
|
|
|
public class MatchQueryBuilder : QueryBuilder {
|
|
|
|
internal MatchQueryBuilder(Database db, Table table) {
|
|
|
|
base(db);
|
|
|
|
if (table.fts_columns == null) error("MATCH query on non FTS table");
|
|
|
|
from(table);
|
2018-06-27 14:58:10 +00:00
|
|
|
join_name(@"_fts_$table_name", @"_fts_$table_name.docid = $table_name.rowid");
|
2018-06-17 23:47:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public MatchQueryBuilder match(Column<string> column, string match) {
|
|
|
|
if (table == null) error("MATCH must occur after FROM statement");
|
|
|
|
if (!(column in table.fts_columns)) error("MATCH selection on non FTS column");
|
|
|
|
selection_args += new StatementBuilder.StringField(match);
|
|
|
|
selection = @"($selection) AND _fts_$table_name.$(column.name) MATCH ?";
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-28 21:48:07 +00:00
|
|
|
}
|