From f14362363d7451cc7ba3ee1f2b3445e76fb65aa0 Mon Sep 17 00:00:00 2001 From: Vinicius Silva Date: Thu, 25 Jan 2024 00:00:51 -0300 Subject: [PATCH 1/5] Method implemented to retrieve the column name and value! --- vlib/db/sqlite/sqlite.c.v | 110 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/vlib/db/sqlite/sqlite.c.v b/vlib/db/sqlite/sqlite.c.v index f06997edc7ca86..e4296ba5fa8a64 100644 --- a/vlib/db/sqlite/sqlite.c.v +++ b/vlib/db/sqlite/sqlite.c.v @@ -1,5 +1,7 @@ module sqlite +import regex { regex_opt } + $if freebsd || openbsd { #flag -I/usr/local/include #flag -L/usr/local/lib @@ -85,6 +87,11 @@ pub mut: vals []string } +pub struct QuerySet { +pub mut: + vals map[string]string +} + // fn C.sqlite3_open(&char, &&C.sqlite3) int @@ -248,6 +255,109 @@ pub fn (db &DB) exec(query string) ![]Row { return rows } +@[manualfree] +pub fn (db &DB) get_queryset(query string) ![]QuerySet { + query_lower := query.to_lower() + + // 'select' syntax verified on: https://www.sqlite.org/lang_select.html and + // https://www.sqlite.org/syntax/join-clause.html + mut select_header := regex_opt(r'select((\s)+(all)|(distinct)(\s)+)|(\s)+')! + mut from := regex_opt(r'(\s)+from(\s)+')! + + // This check eliminates queries that do not have the SELECT statement + if query_lower.count('select') == 1 { + // or do not include 'FROM', just like 'SELECT 1 + 1' + if select_header.matches_string(query_lower) && query_lower.contains(r'from') { + // The execution of this function indicates that the passed + // query is syntactically correct, which is why no additional verification + rows := db.exec(query)! + + defer { + unsafe { rows.free() } + } + + if rows.len == 0 { + return SQLError{ + msg: 'No rows' + code: sqlite.sqlite_done + } + } else { + // Finding final index of select((\s)+(all)|(distinct)(\s)+)|(\s)+ inside query_lower string + _, end_select := select_header.match_string(query_lower) + + // Finding initial and final index of (\s)+from(\s)+ inside query_lower string + init_from, end_from := from.find(query_lower) + + // Get fields possibly separated by ',' like: select field_1, field_2 as f2, from table_name + fields := query.substr(end_select, init_from).split(',') + + mut query_set := []QuerySet{} + mut tuple := map[string]string{} + + if fields[0] == '*' { + table_name := query.substr(end_from, query.len).replace(';', '').split(' ')[0] + + // Get all fields + all_columns_name := db.exec('pragma table_info(${table_name})')! + + defer { + unsafe { all_columns_name.free() } + } + + mut i := 0 + + for row in rows { + for i < row.vals.len { + // position 1 has a database column attribute + tuple[all_columns_name[i].vals[1]] = row.vals[i] + i++ + } + i = 0 + + query_set << QuerySet{tuple} + tuple = map[string]string{} + } + } else { + mut i := 0 + + for row in rows { + for field in fields { + // verifying formats like: + // select column_1 as alias_column_1 from table_name -> alias creation with 'as' + // select column_1 alias_column_1 from table_name -> alias creationg without 'as' + // select column_1 from table_name -> with alias being column_1 + all_field_alias := if field.contains('as') { + field.split('as') + } else { + field.split(' ') + } + + alias := all_field_alias[all_field_alias.len - 1].replace(' ', + '') + tuple[alias] = row.vals[i] + i++ + } + + i = 0 + query_set << QuerySet{tuple} + tuple = map[string]string{} + } + } + + return query_set + } + } else { + return &SQLError{ + msg: 'This is not a selection query' + code: sqlite.sqlite_done + } + } + } else { + return error('Subqueries are not supported!') + } +} + + // exec_one executes a query on the given `db`. // It returns either the first row from the result, if the query was successful, or an error. @[manualfree] From 105f3d671d2cd047dd1c2476fad0bb4b64a7417d Mon Sep 17 00:00:00 2001 From: Vinicius Silva Date: Thu, 25 Jan 2024 00:04:30 -0300 Subject: [PATCH 2/5] Add more tests --- vlib/db/sqlite/sqlite.c.v | 1 - vlib/db/sqlite/sqlite_test.v | 66 ++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/vlib/db/sqlite/sqlite.c.v b/vlib/db/sqlite/sqlite.c.v index e4296ba5fa8a64..3c3458505ec18e 100644 --- a/vlib/db/sqlite/sqlite.c.v +++ b/vlib/db/sqlite/sqlite.c.v @@ -357,7 +357,6 @@ pub fn (db &DB) get_queryset(query string) ![]QuerySet { } } - // exec_one executes a query on the given `db`. // It returns either the first row from the result, if the query was successful, or an error. @[manualfree] diff --git a/vlib/db/sqlite/sqlite_test.v b/vlib/db/sqlite/sqlite_test.v index 4bd5be062deb56..19d85966bfa250 100644 --- a/vlib/db/sqlite/sqlite_test.v +++ b/vlib/db/sqlite/sqlite_test.v @@ -79,6 +79,72 @@ fn test_sqlite() { db.exec("delete from users where name='Sam'")! assert db.get_affected_rows_count() == 1 + db.get_queryset('SELECT * FROM users') or { panic(err) } + assert db.get_affected_rows_count() == 1 + + db.get_queryset('Select * From users') or { panic(err) } + assert db.get_affected_rows_count() == 1 + + db.get_queryset('select * from users') or { panic(err) } + assert db.get_affected_rows_count() == 1 + + db.get_queryset('SELECT * FROM users') or { panic(err) } + assert db.get_affected_rows_count() == 1 + + db.get_queryset('Select * From users') or { panic(err) } + assert db.get_affected_rows_count() == 1 + + db.get_queryset('select * from users') or { panic(err) } + // assert db.get_affected_rows_count() == 1 + + db.get_queryset('SELECT name, id FROM users WHERE id = 3') or { panic(err) } + assert db.get_affected_rows_count() == 1 + + db.get_queryset('Select name as n From users WHERE id = 3') or { panic(err) } + assert db.get_affected_rows_count() == 1 + + db.get_queryset('select name n from users WHERE id = 3') or { panic(err) } + assert db.get_affected_rows_count() == 1 + + db.get_queryset('SELECT name, id FROM users WHERE id = 3') or { panic(err) } + assert db.get_affected_rows_count() == 1 + + db.get_queryset('Select name as name From users WHERE id = 3') or { panic(err) } + assert db.get_affected_rows_count() == 1 + + db.get_queryset('select name n from users WHERE id = 3') or { panic(err) } + assert db.get_affected_rows_count() == 1 + + db.get_queryset('SELECT * FROM users WHERE id = 3') or { panic(err) } + assert db.get_affected_rows_count() == 1 + + db.get_queryset('select id from users WHERE id = 3') or { panic(err) } + assert db.get_affected_rows_count() == 1 + + db.get_queryset('select id as i, name as n from users WHERE id = 3') or { panic(err) } + assert db.get_affected_rows_count() == 1 + + db.get_queryset('select id _id, name _n from users WHERE id = 3') or { panic(err) } + assert db.get_affected_rows_count() == 1 + + db.get_queryset('select max(id), name from users WHERE id = 3') or { panic(err) } + assert db.get_affected_rows_count() == 1 + + db.get_queryset('select max(id) as max, name from users WHERE id = 3') or { panic(err) } + assert db.get_affected_rows_count() == 1 + + db.get_queryset('select max(id) from users WHERE id = 3') or { panic(err) } + assert db.get_affected_rows_count() == 1 + + db.get_queryset('select max(id) max, name n from users WHERE id = 3') or { panic(err) } + assert db.get_affected_rows_count() == 1 + + db.get_queryset('select * from users;') or { panic(err) } + assert db.get_affected_rows_count() == 1 + + db.get_queryset('select * from users ;') or { panic(err) } + assert db.get_affected_rows_count() == 1 + db.close() or { panic(err) } assert !db.is_open } From 89bf0f828a5c608f08398f75424b4d1201e5a977 Mon Sep 17 00:00:00 2001 From: Vinicius Silva Date: Sun, 28 Jan 2024 23:37:43 -0300 Subject: [PATCH 3/5] Add function documentation and returns empty QuerySet rather than SQLError --- vlib/db/sqlite/sqlite.c.v | 6 ++--- vlib/db/sqlite/sqlite_test.v | 48 ++++++++++++++++++------------------ 2 files changed, 26 insertions(+), 28 deletions(-) diff --git a/vlib/db/sqlite/sqlite.c.v b/vlib/db/sqlite/sqlite.c.v index 3c3458505ec18e..a21a9915497f2a 100644 --- a/vlib/db/sqlite/sqlite.c.v +++ b/vlib/db/sqlite/sqlite.c.v @@ -255,6 +255,7 @@ pub fn (db &DB) exec(query string) ![]Row { return rows } +// get_queryset returns the values resulting from a 'SELECT' query and the name of each column. If an alias is provided either through the 'as' command or not, the returned value becomes the alias. @[manualfree] pub fn (db &DB) get_queryset(query string) ![]QuerySet { query_lower := query.to_lower() @@ -277,10 +278,7 @@ pub fn (db &DB) get_queryset(query string) ![]QuerySet { } if rows.len == 0 { - return SQLError{ - msg: 'No rows' - code: sqlite.sqlite_done - } + return []QuerySet{} } else { // Finding final index of select((\s)+(all)|(distinct)(\s)+)|(\s)+ inside query_lower string _, end_select := select_header.match_string(query_lower) diff --git a/vlib/db/sqlite/sqlite_test.v b/vlib/db/sqlite/sqlite_test.v index 19d85966bfa250..fde0da65ac66ff 100644 --- a/vlib/db/sqlite/sqlite_test.v +++ b/vlib/db/sqlite/sqlite_test.v @@ -61,7 +61,7 @@ fn test_sqlite() { assert users.len == 4 code := db.exec_none('vacuum') assert code == 101 - user := db.exec_one('select * from users where id = 3') or { panic(err) } + user := db.exec_one('select * from users where id = 3')! println(user) assert user.vals.len == 2 @@ -79,73 +79,73 @@ fn test_sqlite() { db.exec("delete from users where name='Sam'")! assert db.get_affected_rows_count() == 1 - db.get_queryset('SELECT * FROM users') or { panic(err) } + db.get_queryset('SELECT * FROM users')! assert db.get_affected_rows_count() == 1 - db.get_queryset('Select * From users') or { panic(err) } + db.get_queryset('Select * From users')! assert db.get_affected_rows_count() == 1 - db.get_queryset('select * from users') or { panic(err) } + db.get_queryset('select * from users')! assert db.get_affected_rows_count() == 1 - db.get_queryset('SELECT * FROM users') or { panic(err) } + db.get_queryset('SELECT * FROM users')! assert db.get_affected_rows_count() == 1 - db.get_queryset('Select * From users') or { panic(err) } + db.get_queryset('Select * From users')! assert db.get_affected_rows_count() == 1 - db.get_queryset('select * from users') or { panic(err) } + db.get_queryset('select * from users')! // assert db.get_affected_rows_count() == 1 - db.get_queryset('SELECT name, id FROM users WHERE id = 3') or { panic(err) } + db.get_queryset('SELECT name, id FROM users WHERE id = 3')! assert db.get_affected_rows_count() == 1 - db.get_queryset('Select name as n From users WHERE id = 3') or { panic(err) } + db.get_queryset('Select name as n From users WHERE id = 3')! assert db.get_affected_rows_count() == 1 - db.get_queryset('select name n from users WHERE id = 3') or { panic(err) } + db.get_queryset('select name n from users WHERE id = 3')! assert db.get_affected_rows_count() == 1 - db.get_queryset('SELECT name, id FROM users WHERE id = 3') or { panic(err) } + db.get_queryset('SELECT name, id FROM users WHERE id = 3')! assert db.get_affected_rows_count() == 1 - db.get_queryset('Select name as name From users WHERE id = 3') or { panic(err) } + db.get_queryset('Select name as name From users WHERE id = 3')! assert db.get_affected_rows_count() == 1 - db.get_queryset('select name n from users WHERE id = 3') or { panic(err) } + db.get_queryset('select name n from users WHERE id = 3')! assert db.get_affected_rows_count() == 1 - db.get_queryset('SELECT * FROM users WHERE id = 3') or { panic(err) } + db.get_queryset('SELECT * FROM users WHERE id = 3')! assert db.get_affected_rows_count() == 1 - db.get_queryset('select id from users WHERE id = 3') or { panic(err) } + db.get_queryset('select id from users WHERE id = 3')! assert db.get_affected_rows_count() == 1 - db.get_queryset('select id as i, name as n from users WHERE id = 3') or { panic(err) } + db.get_queryset('select id as i, name as n from users WHERE id = 3')! assert db.get_affected_rows_count() == 1 - db.get_queryset('select id _id, name _n from users WHERE id = 3') or { panic(err) } + db.get_queryset('select id _id, name _n from users WHERE id = 3')! assert db.get_affected_rows_count() == 1 - db.get_queryset('select max(id), name from users WHERE id = 3') or { panic(err) } + db.get_queryset('select max(id), name from users WHERE id = 3')! assert db.get_affected_rows_count() == 1 - db.get_queryset('select max(id) as max, name from users WHERE id = 3') or { panic(err) } + db.get_queryset('select max(id) as max, name from users WHERE id = 3')! assert db.get_affected_rows_count() == 1 - db.get_queryset('select max(id) from users WHERE id = 3') or { panic(err) } + db.get_queryset('select max(id) from users WHERE id = 3')! assert db.get_affected_rows_count() == 1 - db.get_queryset('select max(id) max, name n from users WHERE id = 3') or { panic(err) } + db.get_queryset('select max(id) max, name n from users WHERE id = 3')! assert db.get_affected_rows_count() == 1 - db.get_queryset('select * from users;') or { panic(err) } + db.get_queryset('select * from users;')! assert db.get_affected_rows_count() == 1 - db.get_queryset('select * from users ;') or { panic(err) } + db.get_queryset('select * from users ;')! assert db.get_affected_rows_count() == 1 - db.close() or { panic(err) } + db.close()! assert !db.is_open } From 567310e1df4fb10a61aa2e5cff493ebeafc5b31e Mon Sep 17 00:00:00 2001 From: Vinicius Silva Date: Mon, 29 Jan 2024 18:35:38 -0300 Subject: [PATCH 4/5] Union two 'if' with 'and' --- vlib/db/sqlite/sqlite.c.v | 133 ++++++++++++++++++-------------------- 1 file changed, 64 insertions(+), 69 deletions(-) diff --git a/vlib/db/sqlite/sqlite.c.v b/vlib/db/sqlite/sqlite.c.v index a21a9915497f2a..5d69f62074cae1 100644 --- a/vlib/db/sqlite/sqlite.c.v +++ b/vlib/db/sqlite/sqlite.c.v @@ -264,94 +264,89 @@ pub fn (db &DB) get_queryset(query string) ![]QuerySet { // https://www.sqlite.org/syntax/join-clause.html mut select_header := regex_opt(r'select((\s)+(all)|(distinct)(\s)+)|(\s)+')! mut from := regex_opt(r'(\s)+from(\s)+')! + + // or do not include 'FROM', just like 'SELECT 1 + 1' + if query_lower.count('select') == 1 && query_lower.contains('from') { + // The execution of this function indicates that the passed + // query is syntactically correct, which is why no additional verification + rows := db.exec(query)! + + defer { + unsafe { rows.free() } + } - // This check eliminates queries that do not have the SELECT statement - if query_lower.count('select') == 1 { - // or do not include 'FROM', just like 'SELECT 1 + 1' - if select_header.matches_string(query_lower) && query_lower.contains(r'from') { - // The execution of this function indicates that the passed - // query is syntactically correct, which is why no additional verification - rows := db.exec(query)! + if rows.len == 0 { + return []QuerySet{} + } else { + // Finding final index of select((\s)+(all)|(distinct)(\s)+)|(\s)+ inside query_lower string + _, end_select := select_header.match_string(query_lower) - defer { - unsafe { rows.free() } - } + // Finding initial and final index of (\s)+from(\s)+ inside query_lower string + init_from, end_from := from.find(query_lower) - if rows.len == 0 { - return []QuerySet{} - } else { - // Finding final index of select((\s)+(all)|(distinct)(\s)+)|(\s)+ inside query_lower string - _, end_select := select_header.match_string(query_lower) + // Get fields possibly separated by ',' like: select field_1, field_2 as f2, from table_name + fields := query.substr(end_select, init_from).split(',') - // Finding initial and final index of (\s)+from(\s)+ inside query_lower string - init_from, end_from := from.find(query_lower) + mut query_set := []QuerySet{} + mut tuple := map[string]string{} - // Get fields possibly separated by ',' like: select field_1, field_2 as f2, from table_name - fields := query.substr(end_select, init_from).split(',') + if fields[0] == '*' { + table_name := query.substr(end_from, query.len).replace(';', '').split(' ')[0] - mut query_set := []QuerySet{} - mut tuple := map[string]string{} + // Get all fields + all_columns_name := db.exec('pragma table_info(${table_name})')! - if fields[0] == '*' { - table_name := query.substr(end_from, query.len).replace(';', '').split(' ')[0] + defer { + unsafe { all_columns_name.free() } + } - // Get all fields - all_columns_name := db.exec('pragma table_info(${table_name})')! + mut i := 0 - defer { - unsafe { all_columns_name.free() } + for row in rows { + for i < row.vals.len { + // position 1 has a database column attribute + tuple[all_columns_name[i].vals[1]] = row.vals[i] + i++ } + i = 0 - mut i := 0 - - for row in rows { - for i < row.vals.len { - // position 1 has a database column attribute - tuple[all_columns_name[i].vals[1]] = row.vals[i] - i++ + query_set << QuerySet{tuple} + tuple = map[string]string{} + } + } else { + mut i := 0 + + for row in rows { + for field in fields { + // verifying formats like: + // select column_1 as alias_column_1 from table_name -> alias creation with 'as' + // select column_1 alias_column_1 from table_name -> alias creationg without 'as' + // select column_1 from table_name -> with alias being column_1 + all_field_alias := if field.contains('as') { + field.split('as') + } else { + field.split(' ') } - i = 0 - query_set << QuerySet{tuple} - tuple = map[string]string{} + alias := all_field_alias[all_field_alias.len - 1].replace(' ', + '') + tuple[alias] = row.vals[i] + i++ } - } else { - mut i := 0 - - for row in rows { - for field in fields { - // verifying formats like: - // select column_1 as alias_column_1 from table_name -> alias creation with 'as' - // select column_1 alias_column_1 from table_name -> alias creationg without 'as' - // select column_1 from table_name -> with alias being column_1 - all_field_alias := if field.contains('as') { - field.split('as') - } else { - field.split(' ') - } - - alias := all_field_alias[all_field_alias.len - 1].replace(' ', - '') - tuple[alias] = row.vals[i] - i++ - } - i = 0 - query_set << QuerySet{tuple} - tuple = map[string]string{} - } + i = 0 + query_set << QuerySet{tuple} + tuple = map[string]string{} } - - return query_set - } - } else { - return &SQLError{ - msg: 'This is not a selection query' - code: sqlite.sqlite_done } + + return query_set } } else { - return error('Subqueries are not supported!') + return &SQLError{ + msg: 'This is not a selection query or contains subqueries' + code: sqlite.sqlite_done + } } } From a6662d3546efa25a13e8da0bd138213e7d60579b Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Sat, 3 Feb 2024 13:18:54 +0200 Subject: [PATCH 5/5] run `v fmt -w vlib/db/sqlite/sqlite.c.v` --- vlib/db/sqlite/sqlite.c.v | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vlib/db/sqlite/sqlite.c.v b/vlib/db/sqlite/sqlite.c.v index 5d69f62074cae1..bf0cf76434c209 100644 --- a/vlib/db/sqlite/sqlite.c.v +++ b/vlib/db/sqlite/sqlite.c.v @@ -264,7 +264,7 @@ pub fn (db &DB) get_queryset(query string) ![]QuerySet { // https://www.sqlite.org/syntax/join-clause.html mut select_header := regex_opt(r'select((\s)+(all)|(distinct)(\s)+)|(\s)+')! mut from := regex_opt(r'(\s)+from(\s)+')! - + // or do not include 'FROM', just like 'SELECT 1 + 1' if query_lower.count('select') == 1 && query_lower.contains('from') { // The execution of this function indicates that the passed