From a7f6548bea68ec2b2d8747ec0226b14e9f4db04b Mon Sep 17 00:00:00 2001 From: despiegk Date: Wed, 30 Jul 2025 23:43:41 +0200 Subject: [PATCH] ... --- examples/core/generate.vsh | 2 +- examples/hero/db/psql.vsh | 77 ++++++++++++++----- lib/clients/postgresql_client/.heroscript | 2 +- lib/clients/postgresql_client/cmds.v | 14 ++-- .../postgresql_client_factory_.v | 14 ++-- .../postgresql_client_model.v | 17 ++-- lib/data/encoderhero/decoder.v | 23 +++--- .../postgres_client_decoder_test.v | 56 +++++++------- lib/data/location/db.v | 4 +- lib/data/location/factory.v | 4 +- lib/data/paramsparser/params_reflection.v | 18 +++-- lib/hero/db/hero_db/hero_db.v | 4 +- 12 files changed, 140 insertions(+), 95 deletions(-) diff --git a/examples/core/generate.vsh b/examples/core/generate.vsh index a4f6e955..dc0596fa 100755 --- a/examples/core/generate.vsh +++ b/examples/core/generate.vsh @@ -4,7 +4,7 @@ import freeflowuniverse.herolib.core.generator.generic as generator import freeflowuniverse.herolib.core.pathlib mut args := generator.GeneratorArgs{ - path: '~/code/github/freeflowuniverse/herolib/lib/installers/infra' + path: '~/code/github/freeflowuniverse/herolib/lib/clients/postgresql_client' force: true } diff --git a/examples/hero/db/psql.vsh b/examples/hero/db/psql.vsh index cfcc3a88..92e10a20 100755 --- a/examples/hero/db/psql.vsh +++ b/examples/hero/db/psql.vsh @@ -3,12 +3,15 @@ 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:'test2' +!!postgresql_client.configure + password:'testpass' + name:'test5' user: 'testuser' port: 5432 host: 'localhost' @@ -18,28 +21,62 @@ mut plbook := playbook.new(text: heroscript)! postgresql_client.play(mut plbook)! // Get the configured client -mut db_client := postgresql_client.get(name: 'test2')! +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')! -// } +// 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' +// 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 -// )' +// 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('Creating table users if not exists...') +db_client.exec(create_table_sql)! -// println('Database and table setup completed successfully!') +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")! \ No newline at end of file diff --git a/lib/clients/postgresql_client/.heroscript b/lib/clients/postgresql_client/.heroscript index 542b0711..dbe526fa 100644 --- a/lib/clients/postgresql_client/.heroscript +++ b/lib/clients/postgresql_client/.heroscript @@ -1,6 +1,6 @@ !!hero_code.generate_client name: "postgresql_client" - classname: "PostgresClient" + classname: "PostgresqlClient" hasconfig: true singleton: false default: true diff --git a/lib/clients/postgresql_client/cmds.v b/lib/clients/postgresql_client/cmds.v index 796cb6c6..1d757157 100644 --- a/lib/clients/postgresql_client/cmds.v +++ b/lib/clients/postgresql_client/cmds.v @@ -6,12 +6,12 @@ import freeflowuniverse.herolib.osal.core as osal import os import freeflowuniverse.herolib.ui.console -pub fn (mut self PostgresClient) check() ! { +pub fn (mut self PostgresqlClient) check() ! { mut db := self.db()! db.exec('SELECT version();') or { return error('can\t select version from database.\n${self}') } } -pub fn (mut self PostgresClient) exec(c_ string) ![]pg.Row { +pub fn (mut self PostgresqlClient) exec(c_ string) ![]pg.Row { mut db := self.db()! mut c := c_ if !(c.trim_space().ends_with(';')) { @@ -22,7 +22,7 @@ pub fn (mut self PostgresClient) exec(c_ string) ![]pg.Row { } } -pub fn (mut self PostgresClient) db_exists(name_ string) !bool { +pub fn (mut self PostgresqlClient) db_exists(name_ string) !bool { mut db := self.db()! r := db.exec("SELECT datname FROM pg_database WHERE datname='${name_}';")! if r.len == 1 { @@ -35,7 +35,7 @@ pub fn (mut self PostgresClient) db_exists(name_ string) !bool { return false } -pub fn (mut self PostgresClient) db_create(name_ string) ! { +pub fn (mut self PostgresqlClient) db_create(name_ string) ! { name := texttools.name_fix(name_) mut db := self.db()! if !self.db_exists(name)! { @@ -47,7 +47,7 @@ pub fn (mut self PostgresClient) db_create(name_ string) ! { } } -pub fn (mut self PostgresClient) db_delete(name_ string) ! { +pub fn (mut self PostgresqlClient) db_delete(name_ string) ! { mut db := self.db()! name := texttools.name_fix(name_) self.check()! @@ -60,7 +60,7 @@ pub fn (mut self PostgresClient) db_delete(name_ string) ! { } } -pub fn (mut self PostgresClient) db_names() ![]string { +pub fn (mut self PostgresqlClient) db_names() ![]string { mut res := []string{} sqlstr := "SELECT datname FROM pg_database WHERE datistemplate = false and datname != 'postgres' and datname != 'root';" for row in self.exec(sqlstr)! { @@ -77,7 +77,7 @@ pub mut: dest string } -pub fn (mut self PostgresClient) backup(args BackupParams) ! { +pub fn (mut self PostgresqlClient) backup(args BackupParams) ! { if args.dest == '' { return error('specify the destination please') } diff --git a/lib/clients/postgresql_client/postgresql_client_factory_.v b/lib/clients/postgresql_client/postgresql_client_factory_.v index 0bfc9ad9..a4271490 100644 --- a/lib/clients/postgresql_client/postgresql_client_factory_.v +++ b/lib/clients/postgresql_client/postgresql_client_factory_.v @@ -5,7 +5,7 @@ import freeflowuniverse.herolib.core.playbook { PlayBook } import freeflowuniverse.herolib.ui.console __global ( - postgresql_client_global map[string]&PostgresClient + postgresql_client_global map[string]&PostgresqlClient postgresql_client_default string ) @@ -25,10 +25,10 @@ fn args_get(args_ ArgsGet) ArgsGet { return args } -pub fn get(args_ ArgsGet) !&PostgresClient { +pub fn get(args_ ArgsGet) !&PostgresqlClient { mut context := base.context()! mut args := args_get(args_) - mut obj := PostgresClient{ + mut obj := PostgresqlClient{ name: args.name } if args.name !in postgresql_client_global { @@ -48,11 +48,10 @@ pub fn get(args_ ArgsGet) !&PostgresClient { } // register the config for the future -pub fn set(o PostgresClient) ! { +pub fn set(o PostgresqlClient) ! { set_in_mem(o)! mut context := base.context()! heroscript := heroscript_dumps(o)! - println(heroscript) context.hero_config_set('postgresql_client', o.name, heroscript)! } @@ -73,7 +72,7 @@ pub fn delete(args_ ArgsGet) ! { } // only sets in mem, does not set as config -fn set_in_mem(o PostgresClient) ! { +fn set_in_mem(o PostgresqlClient) ! { mut o2 := obj_init(o)! postgresql_client_global[o.name] = &o2 postgresql_client_default = o.name @@ -84,10 +83,7 @@ pub fn play(mut plbook PlayBook) ! { if install_actions.len > 0 { for install_action in install_actions { heroscript := install_action.heroscript() - println(heroscript) mut obj2 := heroscript_loads(heroscript)! - println('postgresql_client playbook action: ${obj2}') - if true{panic("TODO: implement playbook action for postgresql_client, currently not implemented yet.")} set(obj2)! } } diff --git a/lib/clients/postgresql_client/postgresql_client_model.v b/lib/clients/postgresql_client/postgresql_client_model.v index 38db43bc..b7100bfa 100644 --- a/lib/clients/postgresql_client/postgresql_client_model.v +++ b/lib/clients/postgresql_client/postgresql_client_model.v @@ -9,7 +9,7 @@ pub const version = '0.0.0' const singleton = false const default = true -pub struct PostgresClient { +pub struct PostgresqlClient { mut: db_ ?pg.DB @[skip] pub mut: @@ -17,17 +17,17 @@ pub mut: user string = 'root' port int = 5432 host string = 'localhost' - password string + password string = '' dbname string = 'postgres' } -fn obj_init(obj_ PostgresClient) !PostgresClient { +fn obj_init(obj_ PostgresqlClient) !PostgresqlClient { // never call get here, only thing we can do here is work on object itself mut obj := obj_ return obj } -pub fn (mut self PostgresClient) db() !pg.DB { +pub fn (mut self PostgresqlClient) db() !pg.DB { // console.print_debug(args) mut db := self.db_ or { mut db_ := pg.connect( @@ -43,14 +43,13 @@ pub fn (mut self PostgresClient) db() !pg.DB { return db } - /////////////NORMALLY NO NEED TO TOUCH -pub fn heroscript_dumps(obj PostgresClient) !string { - return encoderhero.encode[PostgresClient](obj)! +pub fn heroscript_dumps(obj PostgresqlClient) !string { + return encoderhero.encode[PostgresqlClient](obj)! } -pub fn heroscript_loads(heroscript string) !PostgresClient { - mut obj := encoderhero.decode[PostgresClient](heroscript)! +pub fn heroscript_loads(heroscript string) !PostgresqlClient { + mut obj := encoderhero.decode[PostgresqlClient](heroscript)! return obj } diff --git a/lib/data/encoderhero/decoder.v b/lib/data/encoderhero/decoder.v index 7dc795e1..57a92ca3 100644 --- a/lib/data/encoderhero/decoder.v +++ b/lib/data/encoderhero/decoder.v @@ -17,24 +17,29 @@ pub fn decode[T](data string) !T { // decode_struct is a generic function that decodes a JSON map into the struct T. fn decode_struct[T](_ T, data string) !T { mut typ := T{} - + // println(data) $if T is $struct { obj_name := texttools.snake_case(T.name.all_after_last('.')) - action_name := 'define.${obj_name}' + mut action_name := '${obj_name}.define' + if !data.contains(action_name) { + action_name = '${obj_name}.configure' + if !data.contains(action_name) { + return error('Data does not contain action name: ${obj_name}.define or ${action_name}') + } + } actions_split := data.split('!!') actions := actions_split.filter(it.starts_with(action_name)) - + // println('actions: ${actions}') mut action_str := '' // action_str := '!!define.${obj_name}' - if actions.len == 0 { - return T{} - } else { + if actions.len > 0 { action_str = actions[0] params_str := action_str.trim_string_left(action_name) - params := paramsparser.parse(params_str)! - typ = params.decode[T]()! + params := paramsparser.parse(params_str) or { + panic('could not parse: ${params_str}\n${err}') + } + typ = params.decode[T](typ)! } - // panic('debuggge ${t_}\n${actions[0]}') // return t_ $for field in T.fields { diff --git a/lib/data/encoderhero/postgres_client_decoder_test.v b/lib/data/encoderhero/postgres_client_decoder_test.v index a2b36cc3..9f366583 100644 --- a/lib/data/encoderhero/postgres_client_decoder_test.v +++ b/lib/data/encoderhero/postgres_client_decoder_test.v @@ -1,7 +1,7 @@ module encoderhero -pub struct PostgresClient { +pub struct PostgresqlClient { pub mut: name string = 'default' user string = 'root' @@ -21,7 +21,7 @@ const postgres_client_complex = " " fn test_postgres_client_decode_blank() ! { - mut client := decode[PostgresClient](postgres_client_blank)! + mut client := decode[PostgresqlClient](postgres_client_blank)! assert client.name == 'default' assert client.user == 'root' assert client.port == 5432 @@ -31,7 +31,7 @@ fn test_postgres_client_decode_blank() ! { } fn test_postgres_client_decode_full() ! { - mut client := decode[PostgresClient](postgres_client_full)! + mut client := decode[PostgresqlClient](postgres_client_full)! assert client.name == 'production' assert client.user == 'app_user' assert client.port == 5433 @@ -41,7 +41,7 @@ fn test_postgres_client_decode_full() ! { } fn test_postgres_client_decode_partial() ! { - mut client := decode[PostgresClient](postgres_client_partial)! + mut client := decode[PostgresqlClient](postgres_client_partial)! assert client.name == 'dev' assert client.user == 'root' // default value assert client.port == 5432 // default value @@ -51,7 +51,7 @@ fn test_postgres_client_decode_partial() ! { } fn test_postgres_client_decode_complex() ! { - mut client := decode[PostgresClient](postgres_client_complex)! + mut client := decode[PostgresqlClient](postgres_client_complex)! assert client.name == 'staging' assert client.user == 'stage_user' assert client.port == 5434 @@ -62,7 +62,7 @@ fn test_postgres_client_decode_complex() ! { fn test_postgres_client_encode_decode_roundtrip() ! { // Test encoding and decoding roundtrip - original := PostgresClient{ + original := PostgresqlClient{ name: 'testdb' user: 'testuser' port: 5435 @@ -72,10 +72,10 @@ fn test_postgres_client_encode_decode_roundtrip() ! { } // Encode to heroscript - encoded := encode[PostgresClient](original)! + encoded := encode[PostgresqlClient](original)! // Decode back from heroscript - decoded := decode[PostgresClient](encoded)! + decoded := decode[PostgresqlClient](encoded)! // Verify roundtrip assert decoded.name == original.name @@ -89,7 +89,7 @@ fn test_postgres_client_encode_decode_roundtrip() ! { fn test_postgres_client_encode() ! { // Test encoding with different configurations test_cases := [ - PostgresClient{ + PostgresqlClient{ name: 'minimal' user: 'root' port: 5432 @@ -97,7 +97,7 @@ fn test_postgres_client_encode() ! { password: '' dbname: 'postgres' }, - PostgresClient{ + PostgresqlClient{ name: 'full_config' user: 'admin' port: 5433 @@ -105,7 +105,7 @@ fn test_postgres_client_encode() ! { password: 'securepass' dbname: 'production' }, - PostgresClient{ + PostgresqlClient{ name: 'localhost_dev' user: 'dev' port: 5432 @@ -116,8 +116,8 @@ fn test_postgres_client_encode() ! { ] for client in test_cases { - encoded := encode[PostgresClient](client)! - decoded := decode[PostgresClient](encoded)! + encoded := encode[PostgresqlClient](client)! + decoded := decode[PostgresqlClient](encoded)! assert decoded.name == client.name assert decoded.user == client.user @@ -130,10 +130,14 @@ fn test_postgres_client_encode() ! { // Play script for interactive testing const play_script = " -# PostgresClient Encode/Decode Play Script -# This script demonstrates encoding and decoding PostgresClient configurations +# PostgresqlClient Encode/Decode Play Script +# This script demonstrates encoding and decoding PostgresqlClient configurations -!!define.postgres_client name:playground user:play_user port:5432 host:localhost password:playpass dbname:playdb +!!define.postgres_client 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 @@ -148,11 +152,11 @@ fn test_play_script() ! { return line.trim(' ') != '' && !line.starts_with('#') }) - mut clients := []PostgresClient{} + mut clients := []PostgresqlClient{} for line in lines { if line.starts_with('!!define.postgres_client') { - client := decode[PostgresClient](line)! + client := decode[PostgresqlClient](line)! clients << client } } @@ -177,12 +181,12 @@ fn test_play_script() ! { // Utility function for manual testing pub fn run_play_script() ! { - println('=== PostgresClient Encode/Decode Play Script ===') - println('Testing encoding and decoding of PostgresClient configurations...') + println('=== PostgresqlClient Encode/Decode Play Script ===') + println('Testing encoding and decoding of PostgresqlClient configurations...') // Test 1: Basic encoding println('\n1. Testing basic encoding...') - client := PostgresClient{ + client := PostgresqlClient{ name: 'example' user: 'example_user' port: 5432 @@ -191,10 +195,10 @@ pub fn run_play_script() ! { dbname: 'example_db' } - encoded := encode[PostgresClient](client)! + encoded := encode[PostgresqlClient](client)! println('Encoded: ${encoded}') - decoded := decode[PostgresClient](encoded)! + decoded := decode[PostgresqlClient](encoded)! println('Decoded name: ${decoded.name}') println('Decoded host: ${decoded.host}') @@ -205,7 +209,7 @@ pub fn run_play_script() ! { // Test 3: Edge cases println('\n3. Testing edge cases...') - edge_client := PostgresClient{ + edge_client := PostgresqlClient{ name: 'edge' user: '' port: 0 @@ -214,8 +218,8 @@ pub fn run_play_script() ! { dbname: '' } - edge_encoded := encode[PostgresClient](edge_client)! - edge_decoded := decode[PostgresClient](edge_encoded)! + edge_encoded := encode[PostgresqlClient](edge_client)! + edge_decoded := decode[PostgresqlClient](edge_encoded)! assert edge_decoded.name == 'edge' assert edge_decoded.user == '' diff --git a/lib/data/location/db.v b/lib/data/location/db.v index d277e776..041f17cc 100644 --- a/lib/data/location/db.v +++ b/lib/data/location/db.v @@ -11,13 +11,13 @@ import freeflowuniverse.herolib.clients.postgresql_client pub struct LocationDB { pub mut: db pg.DB - db_client postgresql_client.PostgresClient + db_client postgresql_client.PostgresqlClient tmp_dir pathlib.Path db_dir pathlib.Path } // new_location_db creates a new LocationDB instance -pub fn new_location_db(mut db_client postgresql_client.PostgresClient, reset bool) !LocationDB { +pub fn new_location_db(mut db_client postgresql_client.PostgresqlClient, reset bool) !LocationDB { mut db_dir := pathlib.get_dir(path: '${os.home_dir()}/hero/var/db/location.db', create: true)! // Create locations database if it doesn't exist diff --git a/lib/data/location/factory.v b/lib/data/location/factory.v index eb8d9766..b8a6956f 100644 --- a/lib/data/location/factory.v +++ b/lib/data/location/factory.v @@ -6,11 +6,11 @@ import freeflowuniverse.herolib.clients.postgresql_client pub struct Location { mut: db LocationDB - db_client postgresql_client.PostgresClient + db_client postgresql_client.PostgresqlClient } // new creates a new Location instance -pub fn new(mut db_client postgresql_client.PostgresClient, reset bool) !Location { +pub fn new(mut db_client postgresql_client.PostgresqlClient, reset bool) !Location { db := new_location_db(mut db_client, reset)! return Location{ db: db diff --git a/lib/data/paramsparser/params_reflection.v b/lib/data/paramsparser/params_reflection.v index ad39e163..383a3b32 100644 --- a/lib/data/paramsparser/params_reflection.v +++ b/lib/data/paramsparser/params_reflection.v @@ -6,17 +6,21 @@ import v.reflection // import freeflowuniverse.herolib.data.encoderhero // TODO: support more field types -pub fn (params Params) decode[T]() !T { +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 - return params.decode_struct[T](T{})! + if args.len > 0 { + return params.decode_struct[T](args[0])! + } else { + return params.decode_struct[T](T{})! + } } -pub fn (params Params) decode_struct[T](_ T) !T { - mut t := T{} +pub fn (params Params) decode_struct[T](start T) !T { + mut t := start $for field in T.fields { $if field.is_enum { - t.$(field.name) = params.get_int(field.name) or { 0 } + 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 { @@ -34,7 +38,7 @@ pub fn (params Params) decode_struct[T](_ T) !T { return t } -pub fn (params Params) decode_value[T](_ T, key string) !T { +pub fn (params Params) decode_value[T](val T, key string) !T { // $if T is $option { // return error("is option") // } @@ -42,7 +46,7 @@ pub fn (params Params) decode_value[T](_ T, key string) !T { // TODO: handle required fields if !params.exists(key) { - return T{} + return val } $if T is string { diff --git a/lib/hero/db/hero_db/hero_db.v b/lib/hero/db/hero_db/hero_db.v index a6d37f39..6c0ef6f5 100644 --- a/lib/hero/db/hero_db/hero_db.v +++ b/lib/hero/db/hero_db/hero_db.v @@ -6,12 +6,12 @@ import freeflowuniverse.herolib.core.texttools // Generic database interface for Hero root objects pub struct HeroDB[T] { - db_client &postgresql_client.PostgresClient + db_client &postgresql_client.PostgresqlClient table_name string } // new creates a new HeroDB instance for a specific type T -pub fn new[T](client &postgresql_client.PostgresClient) HeroDB[T] { +pub fn new[T](client &postgresql_client.PostgresqlClient) HeroDB[T] { mut table_name := '${texttools.snake_case(T.name)}s' // Map dirname from module path module_path := T.name.split('.')