aboutsummaryrefslogtreecommitdiff
path: root/libdino/src/qlite/query_builder.vala
diff options
context:
space:
mode:
authorMarvin W <git@larma.de>2026-08-23 19:32:25 +0200
committerMarvin W <git@larma.de>2026-10-23 16:59:51 +0200
commit98ef191bfb3cc5694b9d1001e46a9b00abbd7195 (patch)
treeb6d30b203266afe44f0b5ae8aaf479960e361273 /libdino/src/qlite/query_builder.vala
parentb0456e13ed35e6ebf684b3a7f4d1cb4b6ec352cc (diff)
Move qlite into libdino
Diffstat (limited to 'libdino/src/qlite/query_builder.vala')
-rw-r--r--libdino/src/qlite/query_builder.vala238
1 files changed, 238 insertions, 0 deletions
diff --git a/libdino/src/qlite/query_builder.vala b/libdino/src/qlite/query_builder.vala
new file mode 100644
index 00000000..40514fd5
--- /dev/null
+++ b/libdino/src/qlite/query_builder.vala
@@ -0,0 +1,238 @@
+using Sqlite;
+
+namespace Dino.Qlite {
+
+public class QueryBuilder : StatementBuilder {
+ private bool single_result;
+
+ // SELECT [...]
+ private string column_selector = "*";
+ private Column[] columns = {};
+
+ // FROM [...]
+ protected Table? table;
+ protected string? table_name;
+
+ // JOIN [...]
+ private string joins = "";
+
+ // WHERE [...]
+ protected string selection = "1";
+ internal StatementBuilder.AbstractField[] selection_args = {};
+
+ // ORDER BY [...]
+ private OrderingTerm[]? order_by_terms = {};
+
+ // GROUP BY [...]
+ private string? group_by_term;
+
+ // LIMIT [...] OFFSET [...]
+ private int limit_val;
+ private int offset_val;
+
+ internal QueryBuilder(Database db) {
+ base(db);
+ }
+
+ public QueryBuilder select(Column[] columns = {}) {
+ this.columns = columns;
+ if (columns.length != 0) {
+ for (int i = 0; i < columns.length; i++) {
+ if (column_selector == "*") {
+ column_selector = columns[i].to_string();
+ } else {
+ column_selector += ", " + columns[i].to_string();
+ }
+ }
+ } else {
+ column_selector = "*";
+ }
+ return this;
+ }
+
+ public QueryBuilder select_string(string column_selector) {
+ this.columns = {};
+ this.column_selector = column_selector;
+ return this;
+ }
+
+ public virtual QueryBuilder from(Table table) {
+ if (this.table_name != null) error("cannot use from() multiple times.");
+ this.table = table;
+ this.table_name = table.name;
+ return this;
+ }
+
+ public virtual QueryBuilder from_name(string table) {
+ this.table_name = table;
+ return this;
+ }
+
+ 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 = {}) {
+ this.selection = @"($(this.selection)) AND ($selection)";
+ foreach (string arg in selection_args) {
+ this.selection_args += new StatementBuilder.StringField(arg);
+ }
+ return this;
+ }
+
+ 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 $column $comp ?";
+ return this;
+ }
+
+ public QueryBuilder with_null<T>(Column<T> column) {
+ selection = @"($selection) AND $column ISNULL";
+ return this;
+ }
+
+ public QueryBuilder without_null<T>(Column<T> column) {
+ selection = @"($selection) AND $column NOT NULL";
+ return this;
+ }
+
+ public QueryBuilder order_by(Column column, string dir = "ASC") {
+ order_by_terms += new OrderingTerm(column, dir);
+ return this;
+ }
+
+ public QueryBuilder order_by_name(string name, string dir) {
+ order_by_terms += new OrderingTerm.by_name(name, dir);
+ return this;
+ }
+
+ public QueryBuilder group_by(Column[] columns) {
+ foreach(Column col in columns) {
+ if (group_by_term == null) {
+ group_by_term = col.to_string();
+ } else {
+ group_by_term += @", $col";
+ }
+ }
+ return this;
+ }
+
+ public QueryBuilder limit(int limit) {
+ if (this.limit_val != 0 && limit > this.limit_val) error("tried to increase an existing limit");
+ this.limit_val = limit;
+ return this;
+ }
+
+ public QueryBuilder offset(int offset) {
+ if (this.limit_val == 0) error("limit required before offset");
+ this.offset_val = offset;
+ return this;
+ }
+
+ public QueryBuilder single() {
+ this.single_result = true;
+ return limit(1);
+ }
+
+ public int64 count() {
+ this.column_selector = @"COUNT($column_selector) AS count";
+ this.single_result = true;
+ return row().get_integer("count");
+ }
+
+ private Row? row_() {
+ if (!single_result) error("query is not suited to return a single row, but row() was called.");
+ return iterator().get_next();
+ }
+
+ public RowOption row() {
+ return new RowOption(row_());
+ }
+
+ public T get<T>(Column<T> field, T def = null) {
+ return row().get(field, def);
+ }
+
+ internal override Statement prepare() {
+ Statement stmt = db.prepare(@"SELECT $column_selector $(table_name == null ? "" : @"FROM $((!) table_name)") $joins WHERE $selection $(group_by_term == null ? "" : @"GROUP BY $group_by_term") $(OrderingTerm.all_to_string(order_by_terms)) $(limit_val > 0 ? @" LIMIT $limit_val OFFSET $offset_val" : "")");
+ for (int i = 0; i < selection_args.length; i++) {
+ selection_args[i].bind(stmt, i+1);
+ }
+ return stmt;
+ }
+
+ public RowIterator iterator() {
+ return new RowIterator.from_query_builder(db, this);
+ }
+
+ class OrderingTerm {
+ Column? column;
+ string column_name;
+ string dir;
+
+ public OrderingTerm(Column column, string dir) {
+ this.column = column;
+ this.column_name = column.to_string();
+ 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";
+ }
+
+ public static string all_to_string(OrderingTerm[]? terms) {
+ if (terms == null || terms.length == 0) return "";
+ string res = "ORDER BY "+terms[0].to_string();
+ for (int i = 1; i < terms.length; i++) {
+ res += @", $(terms[i])";
+ }
+ return res;
+ }
+ }
+}
+
+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);
+ join_name(@"_fts_$table_name", @"_fts_$table_name.docid = $table_name.rowid");
+ }
+
+ 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;
+ }
+}
+
+}