from rdf try import
rdf_blank as _rq_rdf_blank_term,
rdf_iri as _rq_rdf_iri,
rdf_literal as _rq_rdf_literal,
rdf_quad as _rq_rdf_quad,
rdf_type as _rq_rdf_type,
xsd as _rq_xsd;
from db/rowquill/sqlbuilder import
build_limit as _rq_build_limit,
build_order as _rq_build_order,
build_where as _rq_build_where;
from std/net/url import escape as _rq_url_escape;
from std/string import join as _rq_join;
let Number _rq_blank_seq := 0;
function _rq_rdf_require () {
die "db/rowquill RDF export requires optional module rdf"
if _rq_rdf_blank_term ≡ null
or _rq_rdf_iri ≡ null
or _rq_rdf_literal ≡ null
or _rq_rdf_quad ≡ null
or _rq_rdf_type ≡ null
or _rq_xsd ≡ null;
return true;
}
function _rq_rdf_base ( schema ) {
let base := schema.get_base_uri();
die "db/rowquill RDF export requires Schema.base_uri"
if base ≡ null or "" _ base eq "";
return "" _ base;
}
function _rq_rdf_column_datatype ( column ) {
let type := lc( "" _ column{type} );
return _rq_xsd("integer")
if type ~ /^(bigint|int|integer|smallint|tinyint)\b/;
return _rq_xsd("decimal")
if type ~ /^(decimal|numeric)\b/;
return _rq_xsd("double")
if type ~ /^(double|float|real)\b/;
return _rq_xsd("boolean")
if type ~ /^(bool|boolean)\b/;
return _rq_xsd("dateTime")
if type ~ /^(datetime|timestamp)\b/;
return _rq_xsd("date")
if type ~ /^date\b/;
return _rq_xsd("time")
if type ~ /^time\b/;
return _rq_xsd("string");
}
function _rq_rdf_blank () {
_rq_blank_seq++;
return _rq_rdf_blank_term( "rowquill" _ _rq_blank_seq );
}
function _rq_rdf_result ( Array quads, PairList opts ) {
if ( opts.exists("into") ) {
opts{into}.add_quads(quads);
return opts{into};
}
return quads;
}
trait TableClass {
method _table_class () {
return self.get_schema().table( self.get_table_name() );
}
method _column ( String col ) {
for ( let c in self.get_column_metadata() ) {
return c if c{name} eq col;
return c if c.get( "accessor", "" ) eq col;
}
die "No such column: " _ col;
}
method _inflate_column ( String col, value ) {
let c := self._column(col);
return c{inflate}(value) if c.exists("inflate");
return value;
}
method _deflate_column ( String col, value ) {
let c := self._column(col);
return c{deflate}(value) if c.exists("deflate");
return value;
}
method _validate_column ( String col, value, raw_value ) {
let c := self._column(col);
if ( c.get( "required", false ) and raw_value ≡ null ) {
die "Column " _ col _ " is required";
}
for ( let max_length in c.get_all("length") ) {
if ( length raw_value > max_length ) {
die "Column " _ col _
" exceeds maximum length " _ max_length;
}
}
for ( let pattern in c.get_all("pattern") ) {
if ( not ( raw_value ~ pattern ) ) {
die "Column " _ col _ " does not match pattern";
}
}
for ( let validator in c.get_all("validate") ) {
if ( not validator(value) ) {
die "Column " _ col _ " failed validation";
}
}
return true;
}
method _set_column ( String col, value, Boolean raw := false ) {
let c := self._column(col);
if ( c.get( "readonly", false ) and self{in_database} ) {
die "Column " _ col _ " is read-only";
}
let raw_value := raw
? value
: self._deflate_column( c{name}, value );
let validators := c.get_all("validate");
let value_for_validation := raw and validators.length()
? self._inflate_column( c{name}, raw_value )
: value;
self._validate_column(
c{name},
value_for_validation,
raw_value
);
self{column_data}{(c{name})} := raw_value;
self{dirty}{(c{name})} := true;
return self;
}
method _get_column ( String col, Boolean raw := false ) {
let value := self{column_data}{(col)};
return raw ? value : self._inflate_column( col, value );
}
method _relationship_join_pairs ( rel ) {
let pairs := rel{join}.to_Array();
return pairs if rel{join} instanceof PairList;
let ordered := [];
for ( let column in self.get_column_metadata() ) {
for ( let pair in pairs ) {
ordered.push(pair) if self._column(pair.key){name} eq column{name};
}
}
return ordered;
}
method _relationship_conditions ( rel ) {
let conditions := {};
for ( let pair in self._relationship_join_pairs(rel) ) {
let local_col := self._column(pair.key){name};
let value := self._get_column(local_col);
return null if value ≡ null;
conditions{(pair.value)} := value;
}
if ( rel.exists("where") ) {
return { AND: [ conditions, rel{where} ] };
}
return conditions;
}
method _set_relationship ( rel, related ) {
die "Cannot set has_many relationship " _ rel{accessor}
if rel{type} eq "has_many";
die "Cannot set narrowed relationship " _ rel{accessor}
if rel.exists("where");
for ( let pair in self._relationship_join_pairs(rel) ) {
let local_col := self._column(pair.key){name};
let related_col := related._column(pair.value){name};
self._set_column(
local_col,
related._get_column( related_col )
);
}
return self;
}
method _validate_database_constraints ( Array cols ) {
for ( let col in cols ) {
let c := self._column(col);
let value := self{column_data}.get( c{name}, null );
next if value ≡ null;
if ( c.get( "unique", false ) ) {
self._validate_unique_column(c, value);
}
if ( c.exists("exists_in") ) {
self._validate_exists_in(c, value);
}
}
return true;
}
method _validate_required_columns_for_write () {
for ( let c in self.get_column_metadata() ) {
let col := c{name};
if ( c.get( "required", false ) ) {
if (
not self{column_data}.exists(col)
or self{column_data}{(col)} ≡ null
) {
die "Column " _ col _ " is required";
}
}
}
return true;
}
method _has_one ( rel ) {
let conditions := self._relationship_conditions(rel);
return null if conditions ≡ null;
let rows := self.get_schema()
.table(rel{table})
._search_run(conditions, {});
return rows[0] if rows.length() > 0;
return null;
}
method _has_many ( rel ) {
let conditions := self._relationship_conditions(rel);
return [] if conditions ≡ null;
return self.get_schema()
.table(rel{table})
._search_run(conditions, {});
}
method _apply_defaults () {
for ( let c in self.get_column_metadata() ) {
next if not c.exists("default");
next if self{column_data}.exists(c{name});
let value := c{default} instanceof Function
? c{default}(self)
: c{default};
self._set_column( c{name}, value );
}
return self;
}
method _run_hooks ( String name ) {
for ( let hook in self.get_hook_metadata(){(name)} ) {
hook(self);
}
return self;
}
method _rdf_table_iri () {
return _rq_rdf_iri(
_rq_rdf_base(self.get_schema()) _
_rq_url_escape(self.get_table_name())
);
}
method _rdf_column_iri ( String col ) {
return _rq_rdf_iri(
_rq_rdf_base(self.get_schema()) _
_rq_url_escape(self.get_table_name()) _
"#" _
_rq_url_escape(col)
);
}
method _rdf_ref_iri ( rel ) {
let cols := self._relationship_join_pairs(rel).map( function ( pair ) {
return _rq_url_escape(self._column(pair.key){name});
} );
return _rq_rdf_iri(
_rq_rdf_base(self.get_schema()) _
_rq_url_escape(self.get_table_name()) _
"#ref-" _
_rq_join( ";", cols )
);
}
method _rdf_row_node () {
let pkey_names := self._table_class()._primary_key_names();
return _rq_rdf_blank() if pkey_names.length() = 0;
let parts := [];
for ( let pkey_name in pkey_names ) {
die "Missing primary key value for " _ pkey_name
if not self{column_data}.exists(pkey_name)
or self{column_data}{(pkey_name)} ≡ null;
parts.push(
_rq_url_escape(pkey_name) _ "=" _
_rq_url_escape("" _ self{column_data}{(pkey_name)})
);
}
return _rq_rdf_iri(
_rq_rdf_base(self.get_schema()) _
_rq_url_escape(self.get_table_name()) _
"/" _
_rq_join( ";", parts )
);
}
method _rdf_literal_for_column ( column ) {
return _rq_rdf_literal(
"" _ self{column_data}{(column{name})},
"",
_rq_rdf_column_datatype(column),
);
}
method _rdf_reference_quads ( subject ) {
let quads := [];
for ( let rel in self.get_relationship_metadata() ) {
next if rel{type} ne "has_one";
let complete := true;
for ( let pair in self._relationship_join_pairs(rel) ) {
let local_col := self._column(pair.key){name};
if (
not self{column_data}.exists(local_col)
or self._get_column(local_col) == null
) {
complete := false;
}
}
next if not complete;
let related := self._has_one(rel);
die "Cannot map relationship " _ rel{accessor} _
" to RDF: target row not found"
if related ≡ null;
die "Cannot map relationship " _ rel{accessor} _
" to RDF: target table has no primary key"
if related._table_class()._primary_key_names().length() = 0;
quads.push(_rq_rdf_quad(
subject,
self._rdf_ref_iri(rel),
related._rdf_row_node(),
));
}
return quads;
}
method _rdf_extra_quads () {
let quads := [];
for ( let hook in self.get_rdf_hook_metadata() ) {
let extra := hook(self);
die "on_rdf callback must return an Array"
if not ( extra instanceof Array );
for ( let quad in extra ) {
quads.push(quad);
}
}
return quads;
}
method as_rdf ( ... PairList opts ) {
_rq_rdf_require();
_rq_rdf_base(self.get_schema());
let subject := self._rdf_row_node();
let quads := [
_rq_rdf_quad( subject, _rq_rdf_type(), self._rdf_table_iri() ),
];
for ( let column in self.get_column_metadata() ) {
next if not self{column_data}.exists(column{name});
next if self._get_column(column{name}) == null;
quads.push(_rq_rdf_quad(
subject,
self._rdf_column_iri(column{name}),
self._rdf_literal_for_column(column),
));
}
for ( let quad in self._rdf_reference_quads(subject) ) {
quads.push(quad);
}
for ( let quad in self._rdf_extra_quads() ) {
quads.push(quad);
}
return _rq_rdf_result( quads, opts );
}
method _validate_unique_column ( column, value ) {
from std/string import join, sprint;
let binds := [ value ];
let where := [
self._table_class()._sql_identifier(column{name}) _ "=?",
];
if ( self{in_database} ) {
for ( let pkey_name in self._table_class()._primary_key_names() ) {
die "Missing primary key value for " _ pkey_name
if not self{column_data}.exists(pkey_name);
where.push(
self._table_class()._sql_identifier(pkey_name) _ "!=?"
);
binds.push( self{column_data}{(pkey_name)} );
}
}
let sql := sprint(
"SELECT 1 AS found FROM %s WHERE %s",
self._table_class()._sql_table(),
join( " AND ", where ),
);
let sth := self.get_schema().get_dbh().prepare(sql);
sth.execute( ... binds );
die "Column " _ column{name} _ " must be unique"
if sth.next_typed_dict();
return true;
}
method _validate_exists_in ( column, value ) {
from std/string import split, sprint;
let spec := column{exists_in};
let parts := split( spec, ".", 2 );
die "Column " _ column{name} _
" exists_in must be table.column"
if parts.length() ≢ 2
or not ( parts[0] ~ /^[A-Za-z_][A-Za-z0-9_]*$/ )
or not ( parts[1] ~ /^[A-Za-z_][A-Za-z0-9_]*$/ );
let tab := parts[0];
let col := parts[1];
let sql := sprint(
"SELECT 1 AS found FROM %s WHERE %s=?",
self._table_class()._sql_identifier(tab),
self._table_class()._sql_identifier(col),
);
let sth := self.get_schema().get_dbh().prepare(sql);
sth.execute(value);
die "Column " _ column{name} _ " refers to missing " _
column{exists_in} if not sth.next_typed_dict();
return true;
}
method insert () {
from std/string import join, sprint;
die "Cannot insert row already in database"
if self{in_database};
self._apply_defaults();
self._validate_required_columns_for_write();
let cols := self.get_column_metadata().map( function ( c ) {
return c{name};
} ).grep( function ( col ) {
return self{column_data}.exists(col);
} );
die "No column values to insert into " _ self.get_table_name()
if cols.length = 0;
self._validate_database_constraints(cols);
self._run_hooks("before_insert");
let placeholders := cols.map( function ( col ) {
return "?";
} );
let sql := sprint(
"INSERT INTO %s (%s) VALUES (%s)",
self._table_class()._sql_table(),
join( ", ", cols.map( function ( col ) {
return self._table_class()._sql_identifier(col);
} ) ),
join( ", ", placeholders ),
);
let values := cols.map( function ( col ) {
return self{column_data}{(col)};
} );
self.get_schema().get_dbh().prepare(sql).execute( ... values );
self{dirty} := {};
self{in_database} := true;
self._run_hooks("after_insert");
return self;
}
method update () {
from std/string import join, sprint;
die "Cannot update row not in database"
if not self{in_database};
self._validate_required_columns_for_write();
let pkey_names := self._table_class()._primary_key_names();
die "Cannot update table without primary key: " _
self.get_table_name()
if pkey_names.length = 0;
let pkey_lookup := {};
for ( let pkey_name in pkey_names ) {
pkey_lookup{(pkey_name)} := true;
}
let dirty_pkeys := pkey_names.grep( function ( col ) {
return self{dirty}.get( col, false );
} );
die "Cannot update dirty primary key fields: " _
join( ", ", dirty_pkeys )
if dirty_pkeys.length;
let cols := self.get_column_metadata().map( function ( c ) {
return c{name};
} ).grep( function ( col ) {
return self{dirty}.get( col, false )
and not pkey_lookup.exists(col);
} );
return self if cols.length = 0;
self._validate_database_constraints(cols);
self._run_hooks("before_update");
let assignments := cols.map( function ( col ) {
return self._table_class()._sql_identifier(col) _ "=?";
} );
let where := pkey_names.map( function ( col ) {
return self._table_class()._sql_identifier(col) _ "=?";
} );
let sql := sprint(
"UPDATE %s SET %s WHERE %s",
self._table_class()._sql_table(),
join( ", ", assignments ),
join( " AND ", where ),
);
let values := cols.map( function ( col ) {
return self{column_data}{(col)};
} );
for ( let pkey_name in pkey_names ) {
die "Missing primary key value for " _ pkey_name
if not self{column_data}.exists(pkey_name);
values.push( self{column_data}{(pkey_name)} );
}
self.get_schema().get_dbh().prepare(sql).execute( ... values );
self{dirty} := {};
self._run_hooks("after_update");
return self;
}
method delete () {
from std/string import join, sprint;
die "Cannot delete row not in database"
if not self{in_database};
let pkey_names := self._table_class()._primary_key_names();
die "Cannot delete from table without primary key: " _
self.get_table_name() if pkey_names.length = 0;
self._run_hooks("before_delete");
let where := pkey_names.map( function ( col ) {
return self._table_class()._sql_identifier(col) _ "=?";
} );
let sql := sprint(
"DELETE FROM %s WHERE %s",
self._table_class()._sql_table(),
join( " AND ", where ),
);
let values := [];
for ( let pkey_name in pkey_names ) {
die "Missing primary key value for " _ pkey_name
if not self{column_data}.exists(pkey_name);
values.push( self{column_data}{(pkey_name)} );
}
self.get_schema().get_dbh().prepare(sql).execute( ... values );
self{dirty} := {};
self{in_database} := false;
self._run_hooks("after_delete");
return self;
}
method _rq_find ( pkey ) {
const pkey_names := self._table_class()._primary_key_names();
let pkey_values := [];
if ( pkey instanceof Array ) {
let i := 0;
while ( i < pkey_names.length() ) {
pkey_values.push(
self._deflate_column( pkey_names[i], pkey[i] )
);
++i;
}
}
else if ( pkey instanceof Dict or pkey instanceof PairList ) {
for ( let pkey_name in pkey_names ) {
pkey_values.push(
self._deflate_column(
pkey_name,
pkey{(pkey_name)}
)
);
}
}
else if ( pkey_names.length = 1 ) {
pkey_values := [
self._deflate_column( pkey_names[0], pkey )
];
}
else {
die "Expected Array or Dict of primary key values";
}
from std/string import sprint, join;
let sql := sprint(
"SELECT * FROM %s WHERE %s",
self._table_class()._sql_table(),
join( " AND ", pkey_names.map( function ( col ) {
return self._table_class()._sql_identifier(col) _ "=?";
} ) ),
);
let sth := self.get_schema().get_dbh().prepare(sql);
sth.execute( ... pkey_values );
let row := sth.next_typed_dict();
if ( row ) {
return self._table_class()._from_database(row);
}
return null;
}
method _rq_search_opts ( PairList conditions ) {
let opts := conditions.get( "opts", {} );
let clean := conditions.copy;
clean.remove("opts");
return { conditions: clean, opts: opts };
}
method _rq_build_where ( conditions ) {
let table_class := self._table_class();
return _rq_build_where(
conditions,
function ( String col ) {
return table_class._sql_column(col);
},
function ( String col, value ) {
return self._deflate_column( col, value );
},
);
}
method _rq_build_order ( opts ) {
let table_class := self._table_class();
return _rq_build_order(
opts,
function ( String col ) {
return table_class._sql_column(col);
},
);
}
method _rq_search_run ( conditions, opts ) {
from std/string import sprint;
let where := self._rq_build_where(conditions);
let limit := _rq_build_limit(opts);
let binds := where{binds};
for ( let value in limit{binds} ) {
binds.push(value);
}
let sql := sprint(
"SELECT * FROM %s WHERE %s",
self._table_class()._sql_table(),
where{sql},
) _ self._rq_build_order(opts) _ limit{sql};
let sth := self.get_schema().get_dbh().prepare(sql);
sth.execute( ... binds );
let rows := [];
for ( let row in sth.all_typed_dict() ) {
rows.push( self._table_class()._from_database(row) );
}
return rows;
}
method _rq_search ( PairList conditions ) {
let parts := self._rq_search_opts(conditions);
return self._rq_search_run( parts{conditions}, parts{opts} );
}
method _rq_all ( PairList args ) {
return self._rq_search_run( {}, args.get( "opts", {} ) );
}
method _rq_first ( PairList conditions ) {
let parts := self._rq_search_opts(conditions);
let opts := parts{opts}.copy;
opts{limit} := 1;
let rows := self._rq_search_run( parts{conditions}, opts );
return rows.length() ? rows[0] : null;
}
method _rq_count ( PairList conditions ) {
let parts := self._rq_search_opts(conditions);
return self._rq_count_run(parts{conditions});
}
method _rq_exists ( PairList conditions ) {
return self._rq_count(conditions) > 0;
}
method _rq_count_run ( conditions ) {
from std/string import sprint;
let where := self._rq_build_where(conditions);
let sql := sprint(
"SELECT COUNT(*) AS rowquill_count FROM %s WHERE %s",
self._table_class()._sql_table(),
where{sql},
);
let sth := self.get_schema().get_dbh().prepare(sql);
sth.execute( ... where{binds} );
return sth.next_typed_dict(){rowquill_count};
}
method _rq_find_or_create ( PairList opts ) {
let rows := self._rq_search_run( opts{find}, { limit: 1 } );
return rows[0] if rows.length();
let data := {};
for ( let pair in opts{find}.to_Array() ) {
next if pair.key eq "AND" or pair.key eq "OR" or pair.key eq "NOT";
next if pair.value instanceof Array;
data{(pair.key)} := pair.value;
}
for ( let pair in opts.get( "create", {} ).to_Array() ) {
data{(pair.key)} := pair.value;
}
let row := self._table_class()._create_from(data);
row.insert();
return row;
}
method _rq_create_or_update ( PairList opts ) {
let rows := self._rq_search_run( opts{find}, { limit: 1 } );
let row := rows.length()
? rows[0]
: self._table_class()._create_from(opts{find});
for ( let pair in opts.get( "set", {} ).to_Array() ) {
row._set_column( pair.key, pair.value );
}
row.insert_or_update();
return row;
}
method is_dirty () {
return self{dirty}.to_Array().length() > 0;
}
method dirty_fields () {
return self{dirty}.to_Array().grep(
fn pair → pair.value
).map(
fn pair → pair.key
);
}
method mark_clean () {
self{dirty} := {};
return self;
}
method reload () {
die "Cannot reload row not in database"
if not self{in_database};
let fresh := self._table_class().find( self._primary_key_value() );
die "Row no longer exists in database" if fresh ≡ null;
self{column_data} := fresh{column_data}.copy;
self{dirty} := {};
self{in_database} := true;
return self;
}
method _primary_key_value () {
let pkeys := self._table_class()._primary_key_names();
die "Cannot use row without primary key"
if pkeys.length = 0;
if ( pkeys.length = 1 ) {
die "Missing primary key value for " _ pkeys[0]
if not self{column_data}.exists(pkeys[0]);
return self._get_column(pkeys[0]);
}
let values := [];
for ( let pkey in pkeys ) {
die "Missing primary key value for " _ pkey
if not self{column_data}.exists(pkey);
values.push( self._get_column(pkey) );
}
return values;
}
method insert_or_update () {
try { self.insert() } catch (e) { self.update() };
}
method _rq_all_as_rdf ( PairList opts ) {
let quads := [];
let search_opts := opts.get( "opts", {} );
for ( let row in self._rq_search_run( {}, search_opts ) ) {
let export_row := row;
if ( row._table_class()._primary_key_names().length() > 0 ) {
let fresh := row._table_class().find(row._primary_key_value());
export_row := fresh if fresh ≢ null;
}
for ( let quad in export_row.as_rdf() ) {
quads.push(quad);
}
}
return _rq_rdf_result( quads, opts );
}
}
class ClassMaker {
let table;
let schema;
let _cols := [];
let _pkey := [];
let _rels := [];
let _helpers := [];
let _traits := [];
let _rdf_hooks := [];
let _hooks := {
before_insert: [],
after_insert: [],
before_update: [],
after_update: [],
before_delete: [],
after_delete: [],
};
method add_column ( String colname, String type, ... PairList pl ) {
const opts := pl ? pl.copy : {{}};
opts{name} := colname;
opts{type} := type;
_cols.push( opts );
_pkey.push( opts ) if opts{primary};
}
method add_helper ( String methodname, Function cb, ... PairList pl ) {
const opts := pl ? pl.copy : {{}};
opts{name} := methodname;
opts{callback} := cb;
_helpers.push( opts );
}
method add_static ( String methodname, Function cb, ... PairList pl ) {
const opts := pl ? pl.copy : {{}};
opts{name} := methodname;
opts{callback} := cb;
opts{is_static} := true;
_helpers.push( opts );
}
method add_trait ( t ) {
_traits.push( t );
}
method before_insert ( Function cb ) { _hooks{before_insert}.push(cb); }
method after_insert ( Function cb ) { _hooks{after_insert}.push(cb); }
method before_update ( Function cb ) { _hooks{before_update}.push(cb); }
method after_update ( Function cb ) { _hooks{after_update}.push(cb); }
method before_delete ( Function cb ) { _hooks{before_delete}.push(cb); }
method after_delete ( Function cb ) { _hooks{after_delete}.push(cb); }
method on_rdf ( Function cb ) { _rdf_hooks.push(cb); }
method has_one ( ... PairList pl ) {
const opts := pl.copy;
self._validate_relationship_opts(opts);
opts{type} := "has_one";
_rels.push(opts);
}
method has_many ( ... PairList pl ) {
const opts := pl.copy;
self._validate_relationship_opts(opts);
opts{type} := "has_many";
_rels.push(opts);
}
method _validate_relationship_opts ( opts ) {
let join := opts{join};
die "Relationship join must be a Dict or PairList"
if not ( join instanceof Dict or join instanceof PairList );
die "Relationship where must be a Dict or PairList"
if opts.exists("where")
and not (
opts{where} instanceof Dict
or opts{where} instanceof PairList
);
return true;
}
method _make_accessors () {
from std/string import join;
return join( "\n", self{_cols}.map( function (c) {
const colname := c{name};
const accessorname := c{accessor} ?: c{name};
let code := ```
method ${accessorname} ( ... PairList opts ) {
if ( opts.exists("set") ) {
self._set_column(
"${colname}",
opts{set},
opts.get( "raw", false )
);
}
return self._get_column(
"${colname}",
opts.get( "raw", false )
);
}
```;
if ( accessorname ne colname ) {
code _= ```
method ${colname} ( ... PairList opts ) {
return self.${accessorname}( ... opts );
}
```;
}
return code;
} ) );
}
method _make_relationship_accessors () {
from std/string import join;
let i := -1;
return join( "\n", self{_rels}.map( function (r) {
i++;
const accessorname := r{accessor};
if ( r{type} eq "has_one" ) {
return ```
method ${accessorname} ( ... PairList opts ) {
return self._set_relationship(
self.get_relationship_metadata()[${i}],
opts{set}
) if opts.exists("set");
return self._has_one(
self.get_relationship_metadata()[${i}]
);
}
```;
}
return ```
method ${accessorname} ( ... PairList opts ) {
return self._set_relationship(
self.get_relationship_metadata()[${i}],
opts{set}
) if opts.exists("set");
return self._has_many(
self.get_relationship_metadata()[${i}]
);
}
```;
} ) );
}
method _make_helpers () {
from std/string import join;
let i := -1;
return join( "\n", self{_helpers}.map( function (h) {
i++;
let metadata := h{is_static}
? "self._helper_metadata()"
: "self.get_helper_metadata()";
return ```
${ h{is_static} ? "static method" : "method" } ${ h{name} } ( ... Array a, PairList p ) {
return ${metadata}[${i}]{callback}( self, ...a, ...p );
}
```
} ) );
}
method make_class () {
const CODE := do {
from std/string import camel, join;
let class_name := camel( self{table} );
class_name[0] := "TABLE_" _ uc class_name[0];
let i := 0;
let trait_defs := "";
let trait_slug := "";
for ( const t in self{_traits} ) {
trait_defs _= `; const RowquillTrait${i} := TRAITS[${i}]`;
trait_slug _= `, RowquillTrait${i}`;
++i;
}
```
let _cached_pkeys${trait_defs};
class ${class_name} with TableClass${trait_slug} {
let column_data := {};
let dirty := {};
let in_database := false;
method get_schema () {
return SCHEMA;
}
method get_table_name () {
return TABLE_NAME;
}
method get_column_metadata () {
return COLUMNS;
}
method get_relationship_metadata () {
return RELATIONSHIPS;
}
method get_helper_metadata () {
return HELPERS;
}
method get_hook_metadata () {
return HOOKS;
}
method get_rdf_hook_metadata () {
return RDF_HOOKS;
}
static method _schema () {
return SCHEMA;
}
static method _table_name () {
return TABLE_NAME;
}
static method _column_metadata () {
return COLUMNS;
}
static method _helper_metadata () {
return HELPERS;
}
static method _sql_identifier ( String name ) {
die "Invalid SQL identifier: " _ name
if not ( name ~ /^[A-Za-z_][A-Za-z0-9_]*$/ );
return "\"" _ name _ "\"";
}
static method _column_def ( String col ) {
for ( let c in self._column_metadata() ) {
return c if c{name} eq col;
return c if c.get( "accessor", "" ) eq col;
}
die "No such column: " _ col;
}
static method _sql_column ( String col ) {
return self._sql_identifier( self._column_def(col){name} );
}
static method _sql_table () {
return self._sql_identifier( self._table_name() );
}
static method _primary_key_names () {
_cached_pkeys ?:= self._column_metadata().grep(
→ ^^.get( "primary", false )
);
return _cached_pkeys.map( → ^^{name} );
}
static method create ( ... PairList opts ) {
return self._create_from(opts);
}
static method _create_from ( data ) {
let i := new self();
for ( data.to_Array() ) {
i._set_column( ^^.key, ^^.value );
}
i._apply_defaults();
return i;
}
static method _from_database ( Dict row ) {
let i := new self();
for ( let pair in row.to_Array() ) {
i{column_data}{(pair.key)} := pair.value;
}
i{dirty} := {};
i{in_database} := true;
return i;
}
static method find ( pkey ) {
return ( new self() )._rq_find(pkey);
}
static method _search_run ( conditions, opts ) {
return ( new self() )._rq_search_run( conditions, opts );
}
static method search ( ... PairList conditions ) {
return ( new self() )._rq_search(conditions);
}
static method all ( ... PairList args ) {
return ( new self() )._rq_all(args);
}
static method all_as_rdf ( ... PairList args ) {
return ( new self() )._rq_all_as_rdf(args);
}
static method first ( ... PairList conditions ) {
return ( new self() )._rq_first(conditions);
}
static method count ( ... PairList conditions ) {
return ( new self() )._rq_count(conditions);
}
static method exists ( ... PairList conditions ) {
return ( new self() )._rq_exists(conditions);
}
static method _count_run ( conditions ) {
return ( new self() )._rq_count_run(conditions);
}
static method find_or_create ( ... PairList opts ) {
return ( new self() )._rq_find_or_create(opts);
}
static method create_or_update ( ... PairList opts ) {
return ( new self() )._rq_create_or_update(opts);
}
${self._make_accessors()}
${self._make_relationship_accessors()}
${self._make_helpers()}
}
${class_name};
```
};
from std/eval import eval;
const SCHEMA := self{schema};
const TABLE_NAME := self{table};
const COLUMNS := self{_cols};
const RELATIONSHIPS := self{_rels};
const HELPERS := self{_helpers};
const HOOKS := self{_hooks};
const TRAITS := self{_traits};
const RDF_HOOKS := self{_rdf_hooks};
return eval( CODE );
}
}
modules/db/rowquill/tableclass.zzm
rowquill-0.0.2 source code
Package
- Name
- rowquill
- Version
- 0.0.2
- Uploaded
- 2026-06-15 20:42:01
- Repository
- https://github.com/tobyink/zuzu-rowquill
- Dependencies
-
-
std/db>= 0 -
std/eval>= 0 -
std/net/url>= 0 -
std/string>= 0
-
- Metadata
- zuzu-distribution.json
- Archive
- Download .tar.gz