This commit is contained in:
2025-07-31 03:44:44 +02:00
parent a7f6548bea
commit 50a76dd096
12 changed files with 438 additions and 215 deletions

View File

@@ -0,0 +1,64 @@
```v
struct Repo[T] {
db DB
}
struct User {
id int
name string
}
struct Post {
id int
user_id int
title string
body string
}
fn new_repo[T](db DB) Repo[T] {
return Repo[T]{db: db}
}
// This is a generic function. V will generate it for every type it's used with.
fn (r Repo[T]) find_by_id(id int) ?T {
table_name := T.name // in this example getting the name of the type gives us the table name
return r.db.query_one[T]('select * from ${table_name} where id = ?', id)
}
db := new_db()
users_repo := new_repo[User](db) // returns Repo[User]
posts_repo := new_repo[Post](db) // returns Repo[Post]
user := users_repo.find_by_id(1)? // find_by_id[User]
post := posts_repo.find_by_id(1)? // find_by_id[Post]
```
Currently generic function definitions must declare their type parameters, but in future V will infer generic type parameters from single-letter type names in runtime parameter types. This is why find_by_id can omit [T], because the receiver argument r uses a generic type T.
```v
fn compare[T](a T, b T) int {
if a < b {
return -1
}
if a > b {
return 1
}
return 0
}
// compare[int]
println(compare(1, 0)) // Outputs: 1
println(compare(1, 1)) // 0
println(compare(1, 2)) // -1
// compare[string]
println(compare('1', '0')) // Outputs: 1
println(compare('1', '1')) // 0
println(compare('1', '2')) // -1
// compare[f64]
println(compare(1.1, 1.0)) // Outputs: 1
println(compare(1.1, 1.1)) // 0
println(compare(1.1, 1.2)) // -1
```

View File

@@ -1,82 +0,0 @@
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
import freeflowuniverse.herolib.core
import freeflowuniverse.herolib.clients.postgresql_client
import freeflowuniverse.herolib.core.playbook
import freeflowuniverse.herolib.hero.db.hero_db
import freeflowuniverse.herolib.hero.models.circle
// import freeflowuniverse.herolib.core.playcmds
// Configure PostgreSQL client
heroscript := "
!!postgresql_client.configure
password:'testpass'
name:'test5'
user: 'testuser'
port: 5432
host: 'localhost'
dbname: 'testdb'
"
mut plbook := playbook.new(text: heroscript)!
postgresql_client.play(mut plbook)!
// Get the configured client
mut db_client := postgresql_client.get(name: 'test5')!
// println(db_client)
// Check if test database exists, create if not
if !db_client.db_exists('test')! {
println('Creating database test...')
db_client.db_create('test')!
}
// Switch to test database
db_client.dbname = 'test'
// Create table if not exists
create_table_sql := 'CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)'
println('Creating table users if not exists...')
db_client.exec(create_table_sql)!
println('Database and table setup completed successfully!')
// Create HeroDB for Circle type
mut circle_db := hero_db.new[circle.Circle](db_client)
println(circle_db)
if true{panic("sd")}
circle_db.ensure_table()!
// Create and save a circle
mut my_circle := circle.Circle{
name: "Tech Community"
description: "A community for tech enthusiasts"
domain: "tech.example.com"
config: circle.CircleConfig{
max_members: 1000
allow_guests: true
auto_approve: false
theme: "modern"
}
status: circle.CircleStatus.active
}
circle_db.save(&my_circle)!
// Retrieve the circle
retrieved_circle := circle_db.get_by_index({
"domain": "tech.example.com"
})!
// Search circles by status
active_circles := circle_db.search_by_index("status", "active")!

115
examples/hero/db/psql2.vsh Executable file
View File

@@ -0,0 +1,115 @@
#!/usr/bin/env -S v -n -cg -w -gc none -cc tcc -d use_openssl -enable-globals run
// #!/usr/bin/env -S v -n -w -enable-globals run
import freeflowuniverse.herolib.clients.postgresql_client
import freeflowuniverse.herolib.core.playbook
import db.pg
// psql -h /tmp -U myuser -d mydb
mut db := pg.connect(pg.Config{
host: '/tmp'
port: 5432
user: 'myuser'
password: 'mypassword'
dbname: 'mydb'
})!
mut r:=db.exec("select * from users;")!
println(r)
// import freeflowuniverse.herolib.core
// import freeflowuniverse.herolib.hero.db.hero_db
// import freeflowuniverse.herolib.hero.models.circle
// import freeflowuniverse.herolib.core.playcmds
// // Configure PostgreSQL client
// heroscript := "
// !!postgresql_client.configure
// password:'testpass'
// name:'test5'
// user: 'testuser'
// port: 5432
// host: 'localhost'
// dbname: 'testdb'
// "
// mut plbook := playbook.new(text: heroscript)!
// postgresql_client.play(mut plbook)!
// Configure PostgreSQL client
heroscript := "
!!postgresql_client.configure
password:'mypassword'
name:'aaa'
user: 'myuser'
host: '/tmp'
dbname: 'mydb'
"
mut plbook := playbook.new(text: heroscript)!
postgresql_client.play(mut plbook)!
// //Get the configured client
// mut db_client := postgresql_client.get(name: 'test5')!
// println(db_client)
// // Check if test database exists, create if not
// if !db_client.db_exists('test')! {
// println('Creating database test...')
// db_client.db_create('test')!
// }
// // Switch to test database
// db_client.dbname = 'test'
// // Create table if not exists
// create_table_sql := 'CREATE TABLE IF NOT EXISTS users (
// id SERIAL PRIMARY KEY,
// name VARCHAR(100) NOT NULL,
// email VARCHAR(255) UNIQUE NOT NULL,
// created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
// )'
// println('Creating table users if not exists...')
// db_client.exec(create_table_sql)!
// println('Database and table setup completed successfully!')
// // Create HeroDB for Circle type
// mut circle_db := hero_db.new[circle.Circle]()!
// println(circle_db)
// if true{panic("sd")}
// circle_db.ensure_table()!
// // Create and save a circle
// mut my_circle := circle.Circle{
// name: "Tech Community"
// description: "A community for tech enthusiasts"
// domain: "tech.example.com"
// config: circle.CircleConfig{
// max_members: 1000
// allow_guests: true
// auto_approve: false
// theme: "modern"
// }
// status: circle.CircleStatus.active
// }
// circle_db.save(&my_circle)!
// // Retrieve the circle
// retrieved_circle := circle_db.get_by_index({
// "domain": "tech.example.com"
// })!
// // Search circles by status
// active_circles := circle_db.search_by_index("status", "active")!
//https://www.moncefbelyamani.com/how-to-install-postgresql-on-a-mac-with-homebrew-and-lunchy/

View File

@@ -3,6 +3,7 @@ module postgresql_client
import freeflowuniverse.herolib.core.base
import freeflowuniverse.herolib.core.playbook { PlayBook }
import freeflowuniverse.herolib.ui.console
import freeflowuniverse.herolib.data.encoderhero
__global (
postgresql_client_global map[string]&PostgresqlClient
@@ -83,8 +84,10 @@ pub fn play(mut plbook PlayBook) ! {
if install_actions.len > 0 {
for install_action in install_actions {
heroscript := install_action.heroscript()
mut obj2 := heroscript_loads(heroscript)!
set(obj2)!
println(heroscript)
mut obj := encoderhero.decode[PostgresqlClientData](heroscript)!
// mut obj2 := heroscript_loads(heroscript)!
// set(obj2)!
}
}
}

View File

@@ -2,6 +2,7 @@ module postgresql_client
import freeflowuniverse.herolib.data.paramsparser
import freeflowuniverse.herolib.data.encoderhero
import freeflowuniverse.herolib.ui.console
import os
import db.pg
@@ -9,6 +10,7 @@ pub const version = '0.0.0'
const singleton = false
const default = true
@[heap]
pub struct PostgresqlClient {
mut:
db_ ?pg.DB @[skip]
@@ -21,6 +23,17 @@ pub mut:
dbname string = 'postgres'
}
pub struct PostgresqlClientData {
pub mut:
name string = 'default'
user string = 'root'
port int = 5432
host string = 'localhost'
password string = ''
dbname string = 'postgres'
}
fn obj_init(obj_ PostgresqlClient) !PostgresqlClient {
// never call get here, only thing we can do here is work on object itself
mut obj := obj_
@@ -28,7 +41,7 @@ fn obj_init(obj_ PostgresqlClient) !PostgresqlClient {
}
pub fn (mut self PostgresqlClient) db() !pg.DB {
// console.print_debug(args)
console.print_debug(self)
mut db := self.db_ or {
mut db_ := pg.connect(
host: self.host
@@ -50,6 +63,6 @@ pub fn heroscript_dumps(obj PostgresqlClient) !string {
}
pub fn heroscript_loads(heroscript string) !PostgresqlClient {
mut obj := encoderhero.decode[PostgresqlClient](heroscript)!
return obj
mut obj := encoderhero.decode[PostgresqlClientData](heroscript)!
return PostgresqlClient{db_:pg.DB{}}
}

View File

@@ -39,7 +39,7 @@ pub fn encode[T](val T) !string {
// export exports an encoder into encoded heroscript
pub fn (e Encoder) export() !string {
mut script := e.params.export(
pre: '!!define.${e.action_names.join('.')}'
pre: '!!${e.action_names.join('.')}.configure'
indent: ' '
skip_empty: true
)
@@ -119,6 +119,7 @@ pub fn (mut e Encoder) encode_struct[T](t T) ! {
struct_attrs := attrs_get_reflection(mytype)
mut action_name := texttools.snake_case(T.name.all_after_last('.'))
// println('action_name: ${action_name} ${T.name}')
if 'alias' in struct_attrs {
action_name = struct_attrs['alias'].to_lower()
}

View File

@@ -12,12 +12,12 @@ pub mut:
}
const postgres_client_blank = '!!define.postgres_client'
const postgres_client_full = '!!define.postgres_client name:production user:app_user port:5433 host:db.example.com password:secret123 dbname:myapp'
const postgres_client_partial = '!!define.postgres_client name:dev host:localhost password:devpass'
const postgres_client_blank = '!!postgresql_client.configure'
const postgres_client_full = '!!postgresql_client.configure name:production user:app_user port:5433 host:db.example.com password:secret123 dbname:myapp'
const postgres_client_partial = '!!postgresql_client.configure name:dev host:localhost password:devpass'
const postgres_client_complex = "
!!define.postgres_client name:staging user:stage_user port:5434 host:staging.db.com password:stagepass dbname:stagingdb
!!postgresql_client.configure name:staging user:stage_user port:5434 host:staging.db.com password:stagepass dbname:stagingdb
"
fn test_postgres_client_decode_blank() ! {
@@ -74,6 +74,11 @@ fn test_postgres_client_encode_decode_roundtrip() ! {
// Encode to heroscript
encoded := encode[PostgresqlClient](original)!
// println('Encoded heroscript: ${encoded}')
// if true {
// panic("sss")
// }
// Decode back from heroscript
decoded := decode[PostgresqlClient](encoded)!
@@ -133,17 +138,17 @@ const play_script = "
# PostgresqlClient Encode/Decode Play Script
# This script demonstrates encoding and decoding PostgresqlClient configurations
!!define.postgres_client name:playground user:play_user
!!postgresql_client.configure name:playground user:play_user
port:5432
host:localhost
password:playpass
dbname:playdb
# You can also use partial configurations
!!define.postgres_client name:quick_test host:127.0.0.1
!!postgresql_client.configure name:quick_test host:127.0.0.1
# Default configuration (all defaults)
!!define.postgres_client
!!postgresql_client.configure
"
fn test_play_script() ! {
@@ -155,7 +160,7 @@ fn test_play_script() ! {
mut clients := []PostgresqlClient{}
for line in lines {
if line.starts_with('!!define.postgres_client') {
if line.starts_with('!!postgresql_client.configure') {
client := decode[PostgresqlClient](line)!
clients << client
}

View File

@@ -6,29 +6,25 @@ import v.reflection
// import freeflowuniverse.herolib.data.encoderhero
// TODO: support more field types
pub fn (params Params) decode[T](args ...T) !T {
// work around to allow recursive decoding
// otherwise v cant infer generic type for child fields that are structs
if args.len > 0 {
return params.decode_struct[T](args[0])!
} else {
return params.decode_struct[T](T{})!
}
pub fn (params Params) decode[T](args T) !T {
return params.decode_struct[T](args)!
}
pub fn (params Params) decode_struct[T](start T) !T {
mut t := start
mut t := T{}
$for field in T.fields {
$if field.is_enum {
t.$(field.name) = params.get_int(field.name) or { t.$(field.name) }
} $else {
// super annoying didn't find other way, then to ignore options
$if field.is_option {
// For optional fields, if the key exists, decode it. Otherwise, leave it as none.
if params.exists(field.name) {
t.$(field.name) = params.decode_value(t.$(field.name), field.name)!
}
} $else {
if field.name[0].is_capital() {
// embed := params.decode_struct(t.$(field.name))!
t.$(field.name) = params.decode_struct(t.$(field.name))!
// panic("to implement")
} else {
t.$(field.name) = params.decode_value(t.$(field.name), field.name)!
}
@@ -61,9 +57,10 @@ pub fn (params Params) decode_value[T](val T, key string) !T {
return params.get_list(key)!
} $else $if T is []int {
return params.get_list_int(key)!
} $else $if T is []bool {
return params.get_list_bool(key)!
} $else $if T is []u32 {
lst := params.get_list_u32(key)!
return lst
return params.get_list_u32(key)!
} $else $if T is time.Time {
time_str := params.get(key)!
// todo: 'handle other null times'
@@ -86,6 +83,18 @@ pub fn (params Params) decode_value[T](val T, key string) !T {
return T{}
}
pub fn (params Params) get_list_bool(key string) ![]bool {
mut res := []bool{}
val := params.get(key)!
if val.len == 0 {
return res
}
for item in val.split(',') {
res << item.trim_space().bool()
}
return res
}
@[params]
pub struct EncodeArgs {
pub:
@@ -93,13 +102,6 @@ pub:
}
pub fn encode[T](t T, args EncodeArgs) !Params {
$if t is $option {
// unwrap and encode optionals
workaround := t
if workaround != none {
encode(t, args)!
}
}
mut params := Params{}
// struct_attrs := attrs_get_reflection(mytype)
@@ -111,7 +113,27 @@ pub fn encode[T](t T, args EncodeArgs) !Params {
if 'alias' in field_attrs {
key = field_attrs['alias']
}
$if val is string || val is int || val is bool || val is i64 || val is u32
$if field.is_option {
// Handle optional fields
if val != none {
// Unwrap the optional value before type checking and encoding
// Get the unwrapped value using reflection
// This is a workaround for V's reflection limitations with optionals
// We assume that if val != none, then it can be safely unwrapped
// and its underlying type can be determined.
// This might require a more robust way to get the underlying value
// if V's reflection doesn't provide a direct 'unwrap' for generic `val`.
// For now, we'll rely on the type checks below.
// The `val` here is the actual value of the field, which is `?T`.
// We need to check the type of `field.typ` to know what `T` is.
// Revert to simpler handling for optional fields
// Rely on V's string interpolation for optional types
// If val is none, this block will be skipped.
// If val is not none, it will be converted to string.
params.set(key, '${val}')
}
} $else $if val is string || val is int || val is bool || val is i64 || val is u32
|| val is time.Time || val is ourtime.OurTime {
params.set(key, '${val}')
} $else $if field.is_enum {
@@ -140,6 +162,16 @@ pub fn encode[T](t T, args EncodeArgs) !Params {
key: field.name
value: v2
}
} $else $if field.typ is []bool {
mut v2 := ''
for i in val {
v2 += '${i},'
}
v2 = v2.trim(',')
params.params << Param{
key: field.name
value: v2
}
} $else $if field.typ is []u32 {
mut v2 := ''
for i in val {

View File

@@ -11,6 +11,7 @@ struct TestStruct {
liststr []string
listint []int
listbool []bool
listu32 []u32
child TestChild
}
@@ -21,6 +22,7 @@ struct TestChild {
child_liststr []string
child_listint []int
child_listbool []bool
child_listu32 []u32
}
const test_child = TestChild{
@@ -29,6 +31,8 @@ const test_child = TestChild{
child_yesno: false
child_liststr: ['three', 'four']
child_listint: [3, 4]
child_listbool: [true, false]
child_listu32: [u32(5), u32(6)]
}
const test_struct = TestStruct{
@@ -42,9 +46,12 @@ const test_struct = TestStruct{
yesno: true
liststr: ['one', 'two']
listint: [1, 2]
listbool: [true, false]
listu32: [u32(7), u32(8)]
child: test_child
}
const test_child_params = Params{
params: [
Param{
@@ -67,6 +74,14 @@ const test_child_params = Params{
key: 'child_listint'
value: '3,4'
},
Param{
key: 'child_listbool'
value: 'true,false'
},
Param{
key: 'child_listu32'
value: '5,6'
},
]
}
@@ -74,9 +89,6 @@ const test_params = Params{
params: [Param{
key: 'name'
value: 'test'
}, Param{
key: 'nick'
value: 'test_nick'
}, Param{
key: 'birthday'
value: '2012-12-12 00:00:00'
@@ -92,22 +104,64 @@ const test_params = Params{
}, Param{
key: 'listint'
value: '1,2'
}, Param{
key: 'listbool'
value: 'true,false'
}, Param{
key: 'listu32'
value: '7,8'
}, Param{
key: 'child'
value: test_child_params.export()
}]
}
fn test_decode() {
// test single level struct
decoded_child := test_child_params.decode[TestChild]()!
assert decoded_child == test_child
// IMPORTANT OPTIONALS ARE NOT SUPPORTED AND WILL NOT BE ENCODED FOR NOW (unless we find ways how to deal with attributes to not encode skipped elements)
fn test_encode_struct() {
encoded_struct := encode[TestStruct](test_struct)!
assert encoded_struct == test_params
}
// test recursive decode struct with child
decoded := test_params.decode[TestStruct]()!
assert decoded == test_struct
fn test_decode_struct() {
decoded_struct := test_params.decode[TestStruct](TestStruct{})!
assert decoded_struct.name == test_struct.name
assert decoded_struct.birthday.day == test_struct.birthday.day
assert decoded_struct.birthday.month == test_struct.birthday.month
assert decoded_struct.birthday.year == test_struct.birthday.year
assert decoded_struct.number == test_struct.number
assert decoded_struct.yesno == test_struct.yesno
assert decoded_struct.liststr == test_struct.liststr
assert decoded_struct.listint == test_struct.listint
assert decoded_struct.listbool == test_struct.listbool
assert decoded_struct.listu32 == test_struct.listu32
assert decoded_struct.child == test_struct.child
}
fn test_optional_field() {
mut test_struct_with_nick := TestStruct{
name: test_struct.name
nick: 'test_nick'
birthday: test_struct.birthday
number: test_struct.number
yesno: test_struct.yesno
liststr: test_struct.liststr
listint: test_struct.listint
listbool: test_struct.listbool
listu32: test_struct.listu32
child: test_struct.child
}
encoded_struct_with_nick := encode[TestStruct](test_struct_with_nick)!
assert encoded_struct_with_nick.get('nick')! == 'test_nick'
decoded_struct_with_nick := encoded_struct_with_nick.decode[TestStruct](TestStruct{})!
assert decoded_struct_with_nick.nick or { '' } == 'test_nick'
// Test decoding when optional field is not present in params
mut params_without_nick := test_params
params_without_nick.params = params_without_nick.params.filter(it.key != 'nick')
decoded_struct_without_nick := params_without_nick.decode[TestStruct](TestStruct{})!
assert decoded_struct_without_nick.nick == none
}
fn test_encode() {

View File

@@ -2,16 +2,18 @@ module hero_db
import json
import freeflowuniverse.herolib.clients.postgresql_client
import db.pg
import freeflowuniverse.herolib.core.texttools
// Generic database interface for Hero root objects
pub struct HeroDB[T] {
db_client &postgresql_client.PostgresqlClient
pub mut:
db pg.DB
table_name string
}
// new creates a new HeroDB instance for a specific type T
pub fn new[T](client &postgresql_client.PostgresqlClient) HeroDB[T] {
pub fn new[T]() !HeroDB[T] {
mut table_name := '${texttools.snake_case(T.name)}s'
// Map dirname from module path
module_path := T.name.split('.')
@@ -20,8 +22,14 @@ pub fn new[T](client &postgresql_client.PostgresqlClient) HeroDB[T] {
table_name = '${dirname}_${texttools.snake_case(T.name)}'
}
mut dbclient:=postgresql_client.get()!
mut dbcl:=dbclient.db() or {
return error('Failed to connect to database')
}
return HeroDB[T]{
db_client: client
db: dbcl
table_name: table_name
}
}
@@ -48,12 +56,12 @@ pub fn (mut self HeroDB[T]) ensure_table() ! {
)
'
self.db_client.exec(create_sql)!
// self.db.exec(create_sql)!
// Create indexes on index fields
for field in index_fields {
index_sql := 'CREATE INDEX IF NOT EXISTS idx_${self.table_name}_${field} ON ${self.table_name}(${field})'
self.db_client.exec(index_sql)!
// self.db.exec(index_sql)!
}
}
@@ -61,19 +69,15 @@ pub fn (mut self HeroDB[T]) ensure_table() ! {
fn (self HeroDB[T]) get_index_fields() []string {
mut fields := []string{}
$for field in T.fields {
$if field.attributes.len > 0 {
$for attr in field.attributes {
$if attr == 'index' {
if field.attrs.contains('index') {
fields << texttools.snake_case(field.name)
}
}
}
}
return fields
}
// save stores the object T in the database, updating if it already exists
pub fn (mut self HeroDB[T]) save(obj &T) ! {
pub fn (mut self HeroDB[T]) save(obj T) ! {
// Get index values from object
index_data := self.extract_index_values(obj)
@@ -90,17 +94,22 @@ pub fn (mut self HeroDB[T]) save(obj &T) ! {
}
query += params.join(' AND ')
existing := self.db_client.exec(query)!
existing :=self.db.exec(query)!
if existing.len > 0 {
// Update existing record
id := existing[0].vals[0].int()
update_sql := '
UPDATE ${self.table_name}
SET data = \$1, updated_at = CURRENT_TIMESTAMP
WHERE id = \$2
'
self.db_client.exec(update_sql.replace('\$1', "'${json_data}'").replace('\$2', id.str()))!
id_val := existing[0].vals[0] or { return error('no id') }
// id := id_val.int()
println('Updating existing record with ID: ${id_val}')
if true {
panic('sd111')
}
// update_sql := '
// UPDATE ${self.table_name}
// SET data = \$1, updated_at = CURRENT_TIMESTAMP
// WHERE id = \$2
// '
// self.db_client.db()!.exec_param(update_sql, [json_data, id.str()])!
} else {
// Insert new record
mut columns := []string{}
@@ -120,12 +129,12 @@ pub fn (mut self HeroDB[T]) save(obj &T) ! {
INSERT INTO ${self.table_name} (${columns.join(', ')})
VALUES (${values.join(', ')})
'
self.db_client.exec(insert_sql)!
// self.db.exec(insert_sql)!
}
}
// get_by_index retrieves an object T by its index values
pub fn (mut self HeroDB[T]) get_by_index(index_values map[string]string) !&T {
pub fn (mut self HeroDB[T]) get_by_index(index_values map[string]string) !T {
mut query := 'SELECT data FROM ${self.table_name} WHERE '
mut params := []string{}
@@ -134,90 +143,91 @@ pub fn (mut self HeroDB[T]) get_by_index(index_values map[string]string) !&T {
}
query += params.join(' AND ')
rows := self.db_client.exec(query)!
rows := self.db.exec(query)!
if rows.len == 0 {
return error('${T.name} not found with index values: ${index_values}')
}
json_data := rows[0].vals[0].str()
mut obj := json.decode(T, json_data) or {
return error('Failed to decode JSON: ${err}')
json_data_val := rows[0].vals[0] or { return error('no data') }
println('json_data_val: ${json_data_val}')
if true{
panic('sd2221')
}
// mut obj := json.decode(T, json_data_val) or {
// return error('Failed to decode JSON: ${err}')
// }
return &obj
// return &obj
return T{}
}
// get_all retrieves all objects T from the database
pub fn (mut self HeroDB[T]) get_all() ![]&T {
query := 'SELECT data FROM ${self.table_name} ORDER BY id DESC'
rows := self.db_client.exec(query)!
// // get_all retrieves all objects T from the database
// pub fn (mut self HeroDB[T]) get_all() ![]T {
// query := 'SELECT data FROM ${self.table_name} ORDER BY id DESC'
// rows := self.db_client.db()!.exec(query)!
mut results := []&T{}
for row in rows {
json_data := row.vals[0].str()
obj := json.decode(T, json_data) or {
continue // Skip invalid JSON
}
results << &obj
}
// mut results := []T{}
// for row in rows {
// json_data_val := row.vals[0] or { continue }
// json_data := json_data_val.str()
// mut obj := json.decode(T, json_data) or {
// // e.g. an error could be given here
// continue // Skip invalid JSON
// }
// results << &obj
// }
return results
}
// return results
// }
// search_by_index searches for objects T by a specific index field
pub fn (mut self HeroDB[T]) search_by_index(field_name string, value string) ![]&T {
query := 'SELECT data FROM ${self.table_name} WHERE ${field_name} = \'${value}\' ORDER BY id DESC'
rows := self.db_client.exec(query)!
// // search_by_index searches for objects T by a specific index field
// pub fn (mut self HeroDB[T]) search_by_index(field_name string, value string) ![]T {
// query := 'SELECT data FROM ${self.table_name} WHERE ${field_name} = \'${value}\' ORDER BY id DESC'
// rows := self.db_client.db()!.exec(query)!
mut results := []&T{}
for row in rows {
json_data := row.vals[0].str()
obj := json.decode(T, json_data) or {
continue
}
results << &obj
}
// mut results := []T{}
// for row in rows {
// json_data_val := row.vals[0] or { continue }
// json_data := json_data_val.str()
// mut obj := json.decode(T, json_data) or {
// continue
// }
// results << &obj
// }
return results
}
// return results
// }
// delete_by_index removes objects T matching the given index values
pub fn (mut self HeroDB[T]) delete_by_index(index_values map[string]string) ! {
mut query := 'DELETE FROM ${self.table_name} WHERE '
mut params := []string{}
// // delete_by_index removes objects T matching the given index values
// pub fn (mut self HeroDB[T]) delete_by_index(index_values map[string]string) ! {
// mut query := 'DELETE FROM ${self.table_name} WHERE '
// mut params := []string{}
for key, value in index_values {
params << '${key} = \'${value}\''
}
query += params.join(' AND ')
// for key, value in index_values {
// params << '${key} = \'${value}\''
// }
// query += params.join(' AND ')
self.db_client.exec(query)!
}
// self.db_client.db()!.exec(query)!
// }
// Helper to extract index values from object
fn (self HeroDB[T]) extract_index_values(obj &T) map[string]string {
fn (self HeroDB[T]) extract_index_values(obj T) map[string]string {
mut index_data := map[string]string{}
$for field in T.fields {
$if field.attributes.len > 0 {
$for attr in field.attributes {
$if attr == 'index' {
field_name := texttools.snake_case(field.name)
$if field.typ is string {
value := obj.$(field.name).str()
index_data[field_name] = value
} $else $if field.typ is u32 || field.typ is u64 {
value := obj.$(field.name).str()
index_data[field_name] = value
} $else {
// Convert other types to string
value := obj.$(field.name).str()
index_data[field_name] = value
// $if field.attrs.contains('index') {
// field_name := texttools.snake_case(field.name)
// $if field.typ is string {
// value := obj.$(field.name)
// index_data[field_name] = value
// } $else $if field.typ is int {
// value := obj.$(field.name).str()
// index_data[field_name] = value
// } $else {
// value := obj.$(field.name).str()
// index_data[field_name] = value
// }
// }
}
}
}
}
}
return index_data
}

View File

@@ -3,6 +3,7 @@ module circle
import freeflowuniverse.herolib.hero.models.core
// Circle represents a circle entity with configuration and metadata
@[heap]
pub struct Circle {
core.Base
pub mut:

7
logfile Normal file
View File

@@ -0,0 +1,7 @@
2025-07-31 02:25:13.191 CEST [63643] LOG: starting PostgreSQL 17.5 (Homebrew) on aarch64-apple-darwin24.4.0, compiled by Apple clang version 17.0.0 (clang-1700.0.13.3), 64-bit
2025-07-31 02:25:13.192 CEST [63643] LOG: listening on IPv6 address "::1", port 5432
2025-07-31 02:25:13.192 CEST [63643] LOG: listening on IPv4 address "127.0.0.1", port 5432
2025-07-31 02:25:13.192 CEST [63643] LOG: listening on Unix socket "/tmp/.s.PGSQL.5432"
2025-07-31 02:25:13.194 CEST [63646] LOG: database system was shut down at 2025-07-31 02:24:30 CEST
2025-07-31 02:25:13.196 CEST [63643] LOG: database system is ready to accept connections
2025-07-31 02:25:18.216 CEST [63775] FATAL: database "despiegk" does not exist