Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sqlite: Functions thats returns the values resulting from a 'SELECT' query and name of column - Feature ISSUE #20565 #20650

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 109 additions & 0 deletions vlib/db/sqlite/sqlite.c.v
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
module sqlite

import regex { regex_opt }

$if freebsd || openbsd {
#flag -I/usr/local/include
#flag -L/usr/local/lib
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -248,6 +255,108 @@ pub fn (db &DB) exec(query string) ![]Row {
return rows
}

@[manualfree]
pub fn (db &DB) get_queryset(query string) ![]QuerySet {
spytheman marked this conversation as resolved.
Show resolved Hide resolved
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)+')!
Comment on lines +265 to +266
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hm, why is regex matching needed at all?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I use the string query itself to obtain the names of the columns if they have been specified, as well as the name of the table! Since there are several ways to initiate the 'SELECT' command doc, I constructed a regex based on the syntax to facilitate obtaining the column names and the table name!


// 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)!
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rows := db.exec(query)! will return an error itself, if the query has invalid syntax.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, that's why I don't perform any verification other than simply checking if the query is a "select" statement beforehand. If the query isn't a select statement, I don't execute it at all!

That way, I save execution time for a query that isn't even a 'select'.


defer {
unsafe { rows.free() }
}

if rows.len == 0 {
return SQLError{
msg: 'No rows'
code: sqlite.sqlite_done
}
spytheman marked this conversation as resolved.
Show resolved Hide resolved
} 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]
Expand Down
66 changes: 66 additions & 0 deletions vlib/db/sqlite/sqlite_test.v
Original file line number Diff line number Diff line change
Expand Up @@ -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) }
spytheman marked this conversation as resolved.
Show resolved Hide resolved
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
}
Expand Down
Loading