Skip to content

Commit

Permalink
E021-09: TRIM function (#114)
Browse files Browse the repository at this point in the history
`TRIM` can be constructed in several forms:

    TRIM(
      [ [ { LEADING | TRAILING | BOTH } ] [ trim_character ] FROM ]
      trim_source
    )

If `LEADING`, `TRAILING` or `BOTH` is not provided, `BOTH` is used.

If `trim_character` is not provided, a space (`' '`) is used.

    VALUES TRIM('  hello world ');
    -- COL1: hello world

    VALUES TRIM('a' FROM 'aaababccaa');
    -- COL1: babcc

    VALUES TRIM(LEADING 'a' FROM 'aaababccaa');
    -- COL1: babccaa

    VALUES TRIM(TRAILING 'a' FROM 'aaababccaa');
    -- COL1: aaababcc
  • Loading branch information
elliotchance authored Jul 15, 2022
1 parent f84fb96 commit ab3a5e7
Show file tree
Hide file tree
Showing 10 changed files with 413 additions and 3 deletions.
30 changes: 30 additions & 0 deletions docs/functions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,36 @@ be used.
VALUES SUBSTRING('Жabڣc' FROM 4 USING OCTETS);
-- COL1: bڣc
TRIM() CHARACTER VARYING
^^^^^^^^^^^^^^^^^^^^^^^^

``TRIM`` can be constructed in several forms:

.. code-block:: text
TRIM(
[ [ { LEADING | TRAILING | BOTH } ] [ trim_character ] FROM ]
trim_source
)
If ``LEADING``, ``TRAILING`` or ``BOTH`` is not provided, ``BOTH`` is used.

If ``trim_character`` is not provided, a space (`' '`) is used.

.. code-block:: sql
VALUES TRIM(' hello world ');
-- COL1: hello world
VALUES TRIM('a' FROM 'aaababccaa');
-- COL1: babcc
VALUES TRIM(LEADING 'a' FROM 'aaababccaa');
-- COL1: babccaa
VALUES TRIM(TRAILING 'a' FROM 'aaababccaa');
-- COL1: aaababcc
``UPPER(CHARACTER VARYING) CHARACTER VARYING``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Expand Down
4 changes: 2 additions & 2 deletions docs/sql-compliance.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Mandatory Features
------------------

As of the latest version (or at least the version of this documentation)
**vsql supports 54 of the 164 mandatory features** of the
**vsql supports 56 of the 164 mandatory features** of the
`SQL:2016 Standard <https://www.iso.org/standard/63556.html>`_.

.. list-table:: Table 43 — Feature taxonomy and definition for mandatory features
Expand Down Expand Up @@ -70,7 +70,7 @@ As of the latest version (or at least the version of this documentation)
* - ✅ E021-08
- ``UPPER`` and ``LOWER`` functions

* - E021-09
* - E021-09
- ``TRIM`` function

* - ❌ E021-10
Expand Down
1 change: 1 addition & 0 deletions generate-grammar.py
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,7 @@ def parse_tree(text):
# parse_tree("SELECT x , y FROM ( SELECT x , y FROM t1 )")
# parse_tree("VALUES TIMESTAMP '2022-06-30'")
# parse_tree("INSERT INTO foo ( f1 ) VALUES ( TIMESTAMP '2022-06-30' )")
# parse_tree("VALUES TRIM ( 'helloworld' )")

for arg in sys.argv[1:]:
print(arg)
Expand Down
22 changes: 22 additions & 0 deletions grammar.bnf
Original file line number Diff line number Diff line change
Expand Up @@ -955,6 +955,28 @@
<character value function> /* Expr */ ::=
<character substring function>
| <fold>
| <trim function>

<trim function> /* Expr */ ::=
TRIM <left paren> <trim operands> <right paren> -> trim

<trim operands> /* Expr */ ::=
<trim source> -> trim1
| FROM <trim source> -> trim1
| <trim specification> FROM <trim source> -> trim2
| <trim character> FROM <trim source> -> trim3
| <trim specification> <trim character> FROM <trim source> -> trim4

<trim source> /* Expr */ ::=
<character value expression>

<trim specification> /* string */ ::=
LEADING
| TRAILING
| BOTH

<trim character> /* Expr */ ::=
<character value expression>

<fold> /* Expr */ ::=
UPPER <left paren> <character value expression> <right paren> -> upper
Expand Down
44 changes: 44 additions & 0 deletions tests/trim.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
EXPLAIN VALUES TRIM('aaababccaa');
-- EXPLAIN: VALUES (COL1 CHARACTER VARYING) = ROW(TRIM(BOTH ' ' FROM 'aaababccaa'))

EXPLAIN VALUES TRIM(FROM 'aaababccaa');
-- EXPLAIN: VALUES (COL1 CHARACTER VARYING) = ROW(TRIM(BOTH ' ' FROM 'aaababccaa'))

EXPLAIN VALUES TRIM(LEADING FROM 'aaababccaa');
-- EXPLAIN: VALUES (COL1 CHARACTER VARYING) = ROW(TRIM(LEADING ' ' FROM 'aaababccaa'))

EXPLAIN VALUES TRIM(TRAILING FROM 'aaababccaa');
-- EXPLAIN: VALUES (COL1 CHARACTER VARYING) = ROW(TRIM(TRAILING ' ' FROM 'aaababccaa'))

EXPLAIN VALUES TRIM(BOTH FROM 'aaababccaa');
-- EXPLAIN: VALUES (COL1 CHARACTER VARYING) = ROW(TRIM(BOTH ' ' FROM 'aaababccaa'))

EXPLAIN VALUES TRIM('a' FROM 'aaababccaa');
-- EXPLAIN: VALUES (COL1 CHARACTER VARYING) = ROW(TRIM(BOTH 'a' FROM 'aaababccaa'))

EXPLAIN VALUES TRIM(LEADING 'a' FROM 'aaababccaa');
-- EXPLAIN: VALUES (COL1 CHARACTER VARYING) = ROW(TRIM(LEADING 'a' FROM 'aaababccaa'))

VALUES TRIM('aaababccaa');
-- COL1: aaababccaa

VALUES TRIM(FROM 'aaababccaa');
-- COL1: aaababccaa

VALUES TRIM(LEADING FROM 'aaababccaa');
-- COL1: aaababccaa

VALUES TRIM(TRAILING FROM 'aaababccaa');
-- COL1: aaababccaa

VALUES TRIM(BOTH FROM 'aaababccaa');
-- COL1: aaababccaa

VALUES TRIM('a' FROM 'aaababccaa');
-- COL1: babcc

VALUES TRIM(LEADING 'a' FROM 'aaababccaa');
-- COL1: babccaa

VALUES TRIM(TRAILING 'a' FROM 'aaababccaa');
-- COL1: aaababcc
18 changes: 18 additions & 0 deletions vsql/ast.v
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ type Expr = BetweenExpr
| RowExpr
| SimilarExpr
| SubstringExpr
| TrimExpr
| UnaryExpr
| Value

Expand Down Expand Up @@ -103,6 +104,9 @@ fn (e Expr) pstr(params map[string]Value) string {
SubstringExpr {
e.pstr(params)
}
TrimExpr {
e.pstr(params)
}
UnaryExpr {
e.pstr(params)
}
Expand Down Expand Up @@ -553,3 +557,17 @@ fn (e SubstringExpr) pstr(params map[string]Value) string {

return s + ' USING $e.using)'
}

struct TrimExpr {
specification string // LEADING, TRAILING or BOTH
character Expr // NoExpr when missing
source Expr
}

fn (e TrimExpr) str() string {
return e.pstr(map[string]Value{})
}

fn (e TrimExpr) pstr(params map[string]Value) string {
return 'TRIM($e.specification ${e.character.pstr(params)} FROM ${e.source.pstr(params)})'
}
22 changes: 21 additions & 1 deletion vsql/eval.v
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ fn eval_as_type(conn &Connection, data Row, e Expr, params map[string]Value) ?Ty
LocalTimestampExpr {
return new_type('TIMESTAMP WITHOUT TIME ZONE', 0)
}
SubstringExpr {
SubstringExpr, TrimExpr {
return new_type('CHARACTER VARYING', 0)
}
}
Expand Down Expand Up @@ -233,6 +233,9 @@ fn eval_as_value(conn &Connection, data Row, e Expr, params map[string]Value) ?V

return new_timestamp_value(now.strftime('%Y-%m-%d ') + time_value(conn, e.prec, false))
}
TrimExpr {
return eval_trim(conn, data, e, params)
}
}
}

Expand Down Expand Up @@ -324,6 +327,23 @@ fn eval_null(conn &Connection, data Row, e NullExpr, params map[string]Value) ?V
return new_boolean_value(value.is_null())
}

fn eval_trim(conn &Connection, data Row, e TrimExpr, params map[string]Value) ?Value {
source := eval_as_value(conn, data, e.source, params)?
character := eval_as_value(conn, data, e.character, params)?

if e.specification == 'LEADING' {
return new_varchar_value(source.string_value.trim_left(character.string_value),
0)
}

if e.specification == 'TRAILING' {
return new_varchar_value(source.string_value.trim_right(character.string_value),
0)
}

return new_varchar_value(source.string_value.trim(character.string_value), 0)
}

fn eval_like(conn &Connection, data Row, e LikeExpr, params map[string]Value) ?Value {
left := eval_as_value(conn, data, e.left, params)?
right := eval_as_value(conn, data, e.right, params)?
Expand Down
9 changes: 9 additions & 0 deletions vsql/expr.v
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ fn expr_is_agg(conn &Connection, e Expr) ?bool {
return nested_agg_unsupported(e)
}
}
TrimExpr {
if expr_is_agg(conn, e.source)? || expr_is_agg(conn, e.character)? {
return nested_agg_unsupported(e)
}
}
}

return false
Expand Down Expand Up @@ -136,5 +141,9 @@ fn resolve_identifiers(e Expr, tables map[string]Table) ?Expr {
// These don't have any Expr properties to recurse.
return e
}
TrimExpr {
return TrimExpr{e.specification, resolve_identifiers(e.character, tables)?, resolve_identifiers(e.source,
tables)?}
}
}
}
Loading

0 comments on commit ab3a5e7

Please sign in to comment.