mirror of
https://github.com/vlang/v.git
synced 2023-08-10 21:13:21 +03:00
all: like
operator/keyword for V ORM (#18020)
This commit is contained in:
parent
5f870f41b5
commit
3fb32a866c
@ -131,6 +131,7 @@ const (
|
||||
'vlib/orm/orm_interface_test.v',
|
||||
'vlib/orm/orm_mut_db_test.v',
|
||||
'vlib/orm/orm_result_test.v',
|
||||
'vlib/orm/orm_custom_operators_test.v',
|
||||
'vlib/db/sqlite/sqlite_test.v',
|
||||
'vlib/db/sqlite/sqlite_orm_test.v',
|
||||
'vlib/db/sqlite/sqlite_vfs_lowlevel_test.v',
|
||||
@ -208,6 +209,7 @@ const (
|
||||
'vlib/orm/orm_interface_test.v',
|
||||
'vlib/orm/orm_mut_db_test.v',
|
||||
'vlib/orm/orm_result_test.v',
|
||||
'vlib/orm/orm_custom_operators_test.v',
|
||||
'vlib/v/tests/orm_sub_struct_test.v',
|
||||
'vlib/v/tests/orm_sub_array_struct_test.v',
|
||||
'vlib/v/tests/orm_joined_tables_select_test.v',
|
||||
|
@ -59,6 +59,7 @@ pub enum OperationKind {
|
||||
lt // <
|
||||
ge // >=
|
||||
le // <=
|
||||
orm_like // LIKE
|
||||
}
|
||||
|
||||
pub enum MathOperationKind {
|
||||
@ -92,6 +93,7 @@ fn (kind OperationKind) to_str() string {
|
||||
.lt { '<' }
|
||||
.ge { '>=' }
|
||||
.le { '<=' }
|
||||
.orm_like { 'LIKE' }
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
57
vlib/orm/orm_custom_operators_test.v
Normal file
57
vlib/orm/orm_custom_operators_test.v
Normal file
@ -0,0 +1,57 @@
|
||||
import db.sqlite
|
||||
|
||||
struct User {
|
||||
id int [primary; sql: serial]
|
||||
name string
|
||||
country string
|
||||
}
|
||||
|
||||
// like is an example function for checking that V allows using the `like` keyword as an identifier.
|
||||
fn like() {}
|
||||
|
||||
fn test_like_operator() {
|
||||
like()
|
||||
db := sqlite.connect(':memory:')!
|
||||
|
||||
sql db {
|
||||
create table User
|
||||
}!
|
||||
|
||||
luke := User{
|
||||
name: 'Luke'
|
||||
country: 'US'
|
||||
}
|
||||
sql db {
|
||||
insert luke into User
|
||||
}!
|
||||
|
||||
james := User{
|
||||
name: 'James'
|
||||
country: 'UK'
|
||||
}
|
||||
sql db {
|
||||
insert james into User
|
||||
}!
|
||||
|
||||
lukas := User{
|
||||
name: 'Lucas'
|
||||
country: 'DE'
|
||||
}
|
||||
sql db {
|
||||
insert lukas into User
|
||||
}!
|
||||
|
||||
users_with_name_starting_with_letter_l := sql db {
|
||||
select from User where name like 'L%'
|
||||
}!
|
||||
|
||||
assert users_with_name_starting_with_letter_l.len == 2
|
||||
assert users_with_name_starting_with_letter_l.filter(it.name.starts_with('L')).len == 2
|
||||
|
||||
users_with_name_with_second_letter_a := sql db {
|
||||
select from User where name like '_a%'
|
||||
}!
|
||||
|
||||
assert users_with_name_with_second_letter_a.len == 1
|
||||
assert users_with_name_with_second_letter_a.first().name == james.name
|
||||
}
|
@ -457,6 +457,11 @@ fn (mut c Checker) infix_expr(mut node ast.InfixExpr) ast.Type {
|
||||
opt_comp_pos)
|
||||
}
|
||||
}
|
||||
.key_like {
|
||||
node.promoted_type = ast.bool_type
|
||||
|
||||
return c.check_like_operator(node)
|
||||
}
|
||||
.left_shift {
|
||||
if left_final_sym.kind == .array {
|
||||
if !node.is_stmt {
|
||||
@ -757,6 +762,19 @@ fn (mut c Checker) check_div_mod_by_zero(expr ast.Expr, op_kind token.Kind) {
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut c Checker) check_like_operator(node &ast.InfixExpr) ast.Type {
|
||||
if node.left !is ast.Ident || !node.left_type.is_string() {
|
||||
c.error('the left operand of the `like` operator must be an identifier with a string type',
|
||||
node.left.pos())
|
||||
}
|
||||
|
||||
if !node.right_type.is_string() {
|
||||
c.error('the right operand of the `like` operator must be a string type', node.right.pos())
|
||||
}
|
||||
|
||||
return node.promoted_type
|
||||
}
|
||||
|
||||
fn (mut c Checker) invalid_operator_error(op token.Kind, left_type ast.Type, right_type ast.Type, pos token.Pos) {
|
||||
left_name := c.table.type_to_str(left_type)
|
||||
right_name := c.table.type_to_str(right_type)
|
||||
|
6
vlib/v/checker/tests/like_operator_outside_orm_error.out
Normal file
6
vlib/v/checker/tests/like_operator_outside_orm_error.out
Normal file
@ -0,0 +1,6 @@
|
||||
vlib/v/checker/tests/like_operator_outside_orm_error.vv:4:15: error: unexpected name `like`, expecting `,`
|
||||
2 | name := 'Luke'
|
||||
3 |
|
||||
4 | println(name like 'L%')
|
||||
| ~~~~
|
||||
5 | }
|
5
vlib/v/checker/tests/like_operator_outside_orm_error.vv
Normal file
5
vlib/v/checker/tests/like_operator_outside_orm_error.vv
Normal file
@ -0,0 +1,5 @@
|
||||
fn main() {
|
||||
name := 'Luke'
|
||||
|
||||
println(name like 'L%')
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
vlib/v/checker/tests/like_operator_with_non_string_type_error.vv:10:36: error: the right operand of the `like` operator must be a string type
|
||||
8 |
|
||||
9 | println(sql db {
|
||||
10 | select from User where name like 10
|
||||
| ~~
|
||||
11 | }!)
|
||||
12 |
|
||||
vlib/v/checker/tests/like_operator_with_non_string_type_error.vv:14:26: error: the left operand of the `like` operator must be an identifier with a string type
|
||||
12 |
|
||||
13 | println(sql db {
|
||||
14 | select from User where 10 like true
|
||||
| ~~
|
||||
15 | }!)
|
||||
16 | }
|
@ -0,0 +1,16 @@
|
||||
struct User {
|
||||
id int [primary]
|
||||
name string
|
||||
}
|
||||
|
||||
fn main() {
|
||||
db := ''
|
||||
|
||||
println(sql db {
|
||||
select from User where name like 10
|
||||
}!)
|
||||
|
||||
println(sql db {
|
||||
select from User where 10 like true
|
||||
}!)
|
||||
}
|
@ -388,6 +388,9 @@ fn (mut g Gen) sql_where_data(expr ast.Expr, mut fields []string, mut parenthese
|
||||
.le {
|
||||
'orm__OperationKind__le'
|
||||
}
|
||||
.key_like {
|
||||
'orm__OperationKind__orm_like'
|
||||
}
|
||||
else {
|
||||
''
|
||||
}
|
||||
|
@ -466,6 +466,9 @@ pub fn (mut p Parser) expr_with_left(left ast.Expr, precedence int, is_stmt_iden
|
||||
if p.inside_asm && p.prev_tok.pos().line_nr < p.tok.pos().line_nr {
|
||||
return node
|
||||
}
|
||||
|
||||
p.process_custom_orm_operators()
|
||||
|
||||
// Infix
|
||||
for precedence < p.tok.kind.precedence() {
|
||||
if p.tok.kind == .dot {
|
||||
@ -783,3 +786,25 @@ fn (mut p Parser) prefix_inc_dec_error() {
|
||||
p.error_with_pos('prefix `${op}${expr}` is unsupported, use suffix form `${expr}${op}`',
|
||||
full_expr_pos)
|
||||
}
|
||||
|
||||
// process_custom_orm_operators checks whether a word in infix expressions is an ORM operator.
|
||||
// If it is, then a new kind is assigned to it, so that the parser will process it as a keyword.
|
||||
// This is necessary to ensure that parts of the ORM expression do not function
|
||||
// outside of the ORM and are not recognized as keywords in the language.
|
||||
// For example, there is a `like` operator in ORM, which should be used
|
||||
// in expressions like `name like 'M%'`, but it should not be used in V directly.
|
||||
[inline]
|
||||
fn (mut p Parser) process_custom_orm_operators() {
|
||||
if !p.inside_orm {
|
||||
return
|
||||
}
|
||||
|
||||
is_like_operator := p.tok.kind == .name && p.tok.lit == 'like'
|
||||
|
||||
if is_like_operator {
|
||||
p.tok = token.Token{
|
||||
...p.tok
|
||||
kind: .key_like
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import v.ast
|
||||
|
||||
fn (mut p Parser) sql_expr() ast.Expr {
|
||||
tmp_inside_match := p.inside_match
|
||||
p.inside_orm = true
|
||||
p.inside_match = true
|
||||
// `sql db {`
|
||||
pos := p.tok.pos()
|
||||
@ -83,6 +84,7 @@ fn (mut p Parser) sql_expr() ast.Expr {
|
||||
|
||||
p.check(.rcbr)
|
||||
p.inside_match = false
|
||||
p.inside_orm = false
|
||||
or_expr := p.parse_sql_or_block()
|
||||
p.inside_match = tmp_inside_match
|
||||
|
||||
@ -114,8 +116,10 @@ fn (mut p Parser) sql_expr() ast.Expr {
|
||||
// update User set nr_oders=nr_orders+1 where id == user_id
|
||||
fn (mut p Parser) sql_stmt() ast.SqlStmt {
|
||||
mut pos := p.tok.pos()
|
||||
p.inside_orm = true
|
||||
p.inside_match = true
|
||||
defer {
|
||||
p.inside_orm = false
|
||||
p.inside_match = false
|
||||
}
|
||||
// `sql db {`
|
||||
|
@ -63,6 +63,7 @@ mut:
|
||||
inside_struct_field_decl bool
|
||||
inside_struct_attr_decl bool
|
||||
inside_map_init bool
|
||||
inside_orm bool
|
||||
or_is_handled bool // ignore `or` in this expression
|
||||
builtin_mod bool // are we in the `builtin` module?
|
||||
mod string // current module name
|
||||
|
@ -3,13 +3,15 @@
|
||||
// that can be found in the LICENSE file.
|
||||
module token
|
||||
|
||||
const orm_custom_operators = ['like']
|
||||
|
||||
[minify]
|
||||
pub struct Token {
|
||||
pub:
|
||||
kind Kind // the token number/enum; for quick comparisons
|
||||
lit string // literal representation of the token
|
||||
line_nr int // the line number in the source where the token occured
|
||||
col int // the column in the source where the token occured
|
||||
line_nr int // the line number in the source where the token occurred
|
||||
col int // the column in the source where the token occurred
|
||||
// name_idx int // name table index for O(1) lookup
|
||||
pos int // the position of the token in scanner text
|
||||
len int // length of the literal
|
||||
@ -115,6 +117,7 @@ pub enum Kind {
|
||||
key_none
|
||||
key_return
|
||||
key_select
|
||||
key_like
|
||||
key_sizeof
|
||||
key_isreftype
|
||||
key_likely
|
||||
@ -192,12 +195,18 @@ pub const (
|
||||
|
||||
pub const scanner_matcher = new_keywords_matcher_trie[Kind](keywords)
|
||||
|
||||
// build_keys genereates a map with keywords' string values:
|
||||
// build_keys generates a map with keywords' string values:
|
||||
// Keywords['return'] == .key_return
|
||||
fn build_keys() map[string]Kind {
|
||||
mut res := map[string]Kind{}
|
||||
for t in int(Kind.keyword_beg) + 1 .. int(Kind.keyword_end) {
|
||||
key := token.token_str[t]
|
||||
|
||||
// Exclude custom ORM operators from V keyword list
|
||||
if key in token.orm_custom_operators {
|
||||
continue
|
||||
}
|
||||
|
||||
res[key] = unsafe { Kind(t) }
|
||||
}
|
||||
return res
|
||||
@ -315,6 +324,7 @@ fn build_token_str() []string {
|
||||
s[Kind.key_defer] = 'defer'
|
||||
s[Kind.key_match] = 'match'
|
||||
s[Kind.key_select] = 'select'
|
||||
s[Kind.key_like] = 'like'
|
||||
s[Kind.key_none] = 'none'
|
||||
s[Kind.key_nil] = 'nil'
|
||||
s[Kind.key_offsetof] = '__offsetof'
|
||||
@ -426,13 +436,14 @@ pub fn build_precedences() []Precedence {
|
||||
p[Kind.minus] = .sum
|
||||
p[Kind.pipe] = .sum
|
||||
p[Kind.xor] = .sum
|
||||
// `==` | `!=` | `<` | `<=` | `>` | `>=`
|
||||
// `==` | `!=` | `<` | `<=` | `>` | `>=` | `like`
|
||||
p[Kind.eq] = .eq
|
||||
p[Kind.ne] = .eq
|
||||
p[Kind.lt] = .eq
|
||||
p[Kind.le] = .eq
|
||||
p[Kind.gt] = .eq
|
||||
p[Kind.ge] = .eq
|
||||
p[Kind.key_like] = .eq
|
||||
// `=` | `+=` | ...
|
||||
p[Kind.assign] = .assign
|
||||
p[Kind.plus_assign] = .assign
|
||||
@ -504,7 +515,7 @@ pub fn (kind Kind) is_prefix() bool {
|
||||
pub fn (kind Kind) is_infix() bool {
|
||||
return kind in [.plus, .minus, .mod, .mul, .div, .eq, .ne, .gt, .lt, .key_in, .key_as, .ge,
|
||||
.le, .logical_or, .xor, .not_in, .key_is, .not_is, .and, .dot, .pipe, .amp, .left_shift,
|
||||
.right_shift, .unsigned_right_shift, .arrow]
|
||||
.right_shift, .unsigned_right_shift, .arrow, .key_like]
|
||||
}
|
||||
|
||||
[inline]
|
||||
@ -611,6 +622,7 @@ pub fn kind_to_string(k Kind) string {
|
||||
.key_none { 'key_none' }
|
||||
.key_return { 'key_return' }
|
||||
.key_select { 'key_select' }
|
||||
.key_like { 'key_like' }
|
||||
.key_sizeof { 'key_sizeof' }
|
||||
.key_isreftype { 'key_isreftype' }
|
||||
.key_likely { 'key_likely' }
|
||||
@ -733,6 +745,7 @@ pub fn kind_from_string(s string) !Kind {
|
||||
'key_none' { .key_none }
|
||||
'key_return' { .key_return }
|
||||
'key_select' { .key_select }
|
||||
'key_like' { .key_like }
|
||||
'key_sizeof' { .key_sizeof }
|
||||
'key_isreftype' { .key_isreftype }
|
||||
'key_likely' { .key_likely }
|
||||
|
Loading…
Reference in New Issue
Block a user