diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3740556..a6e5c13 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,6 +32,7 @@ jobs: - name: Verify docs run: | + export PIP_BREAK_SYSTEM_PACKAGES=1 pip install sphinx sphinx_rtd_theme cd docs && python3 -m pip install -r requirements.txt && cd - make clean-docs docs diff --git a/.gitignore b/.gitignore index b839895..511ce6e 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,8 @@ vsql-server *.vsql .vscode .vlang_tmp_build/ -grammar.bnf -vsql/grammar.v scripts/generate-v-client-library-docs +scripts/generate-grammar +y.output +vsql/y.v +vsql/y.y diff --git a/Makefile b/Makefile index a194e11..41cbd40 100644 --- a/Makefile +++ b/Makefile @@ -23,16 +23,16 @@ ready: grammar fmt snippets # Binaries -bin/vsql: vsql/grammar.v +bin/vsql: vsql/y.v mkdir -p bin v $(BUILD_OPTIONS) $(PROD) cmd/vsql -o bin/vsql -bin/vsql.exe: vsql/grammar.v +bin/vsql.exe: vsql/y.v mkdir -p bin v -os windows $(BUILD_OPTIONS) $(PROD) cmd/vsql mv cmd/vsql/vsql.exe bin/vsql.exe -oldv: vsql/grammar.v +oldv: vsql/y.v ifdef OLDV @mkdir -p /tmp/oldv/ @# VJOBS and VFLAGS needs to be provided for macOS. I'm not sure if they also @@ -54,19 +54,18 @@ docs: snippets clean-docs: cd docs && make clean -# Grammar (BNF) +# Grammar -grammar.bnf: - grep "//~" -r vsql | cut -d~ -f2 > grammar.bnf +vsql/y.y: + ./scripts/generate-grammar.vsh -vsql/grammar.v: grammar.bnf - python3 generate-grammar.py - v fmt -w vsql/grammar.v +vsql/y.v: vsql/y.y + v run scripts/vyacc.v -o vsql/y.v vsql/y.y clean-grammar: - rm -f grammar.bnf vsql/grammar.v + rm -f vsql/y.v vsql/y.y -grammar: clean-grammar vsql/grammar.v +grammar: clean-grammar vsql/y.v # Formatting @@ -87,6 +86,9 @@ btree-test: oldv sql-test: oldv $(V) -stats $(BUILD_OPTIONS) vsql/sql_test.v +orm-test: oldv + $(V) -stats $(BUILD_OPTIONS) vsql/orm_test.v + # CLI Tests cli-test: bin/vsql @@ -104,7 +106,7 @@ examples: echo $$f; v run $$f || exit 1; \ done -examples/%: vsql/grammar.v +examples/%: vsql/y.v v run examples/$*.v # Benchmarking diff --git a/cmd/tests/catalogs.sh b/cmd/tests/catalogs.sh index 30acd48..5f894e4 100755 --- a/cmd/tests/catalogs.sh +++ b/cmd/tests/catalogs.sh @@ -12,8 +12,8 @@ echo 'INSERT INTO foo (bar) VALUES (123);' | $VSQL cli $VSQL1_FILE echo 'CREATE TABLE foo (baz INT);' | $VSQL cli $VSQL2_FILE echo 'INSERT INTO foo (baz) VALUES (456);' | $VSQL cli $VSQL2_FILE -echo 'SELECT * FROM "file1".public.foo;' | $VSQL cli $VSQL1_FILE $VSQL2_FILE > $TXT_FILE -echo 'SELECT * FROM "file2".public.foo;' | $VSQL cli $VSQL1_FILE $VSQL2_FILE >> $TXT_FILE +echo "SET CATALOG 'file1'; SELECT * FROM public.foo;" | $VSQL cli $VSQL1_FILE $VSQL2_FILE > $TXT_FILE +echo "SET CATALOG 'file2'; SELECT * FROM public.foo;" | $VSQL cli $VSQL1_FILE $VSQL2_FILE >> $TXT_FILE grep -R "BAR: 123" $TXT_FILE grep -R "BAZ: 456" $TXT_FILE diff --git a/cmd/vsql/bench.v b/cmd/vsql/bench.v index 7ae2889..ece3686 100644 --- a/cmd/vsql/bench.v +++ b/cmd/vsql/bench.v @@ -5,14 +5,14 @@ import vsql fn register_bench_command(mut cmd cli.Command) { mut bench_cmd := cli.Command{ - name: 'bench' + name: 'bench' description: 'Run benchmark' - execute: bench_command + execute: bench_command } bench_cmd.add_flag(cli.Flag{ - flag: .string - name: 'file' - abbrev: 'f' + flag: .string + name: 'file' + abbrev: 'f' description: 'File path that will be deleted and created for the test. You can use :memory: as well (default bench.vsql)' }) cmd.add_command(bench_cmd) diff --git a/cmd/vsql/cli.v b/cmd/vsql/cli.v index 09d4fec..ece2fdd 100644 --- a/cmd/vsql/cli.v +++ b/cmd/vsql/cli.v @@ -7,11 +7,11 @@ import vsql fn register_cli_command(mut cmd cli.Command) { mut cli_cmd := cli.Command{ - name: 'cli' - usage: '' + name: 'cli' + usage: '' required_args: 1 - description: 'Run SQL in a vsql file' - execute: cli_command + description: 'Run SQL in a vsql file' + execute: cli_command } cmd.add_command(cli_cmd) } @@ -32,39 +32,50 @@ fn cli_command(cmd cli.Command) ! { print('vsql> ') os.flush() - query := os.get_line() + raw_query := os.get_line() // When running on Docker, ctrl+C doesn't always get passed through. Also, // this provides another text based way to break out of the shell. - if query.trim_space() == 'exit' { + if raw_query.trim_space() == 'exit' { break } - if query != '' { - start := time.ticks() - db.clear_warnings() - result := db.query(query) or { - print_error('Error', err) - continue - } + if raw_query != '' { + // TODO: This is a very poor way to handle multiple queries. + for i, query in raw_query.split(';') { + if query.trim_space() == '' { + continue + } - for warning in db.warnings { - print_error('Warning', warning) - } + start := time.ticks() + db.clear_warnings() + result := db.query(query) or { + print_error('Error', err) + continue + } - mut total_rows := 0 - for row in result { - for column in result.columns { - print('${column.name.sub_entity_name}: ${row.get_string(column.name.sub_entity_name)!} ') + for warning in db.warnings { + print_error('Warning', warning) } - total_rows++ - } - if total_rows > 0 { - println('') - } + mut total_rows := 0 + for row in result { + for column in result.columns { + print('${column.name.sub_entity_name}: ${row.get_string(column.name.sub_entity_name)!} ') + } + total_rows++ + } + + if total_rows > 0 { + println('') + } - println('${total_rows} ${vsql.pluralize(total_rows, 'row')} (${time.ticks() - start} ms)') + println('${total_rows} ${vsql.pluralize(total_rows, 'row')} (${time.ticks() - start} ms)') + + if i > 0 { + println('') + } + } } else { // This means there is no more input and should only occur when the // commands are being few through a pipe like: diff --git a/cmd/vsql/in.v b/cmd/vsql/in.v index 2fce7e9..65edf80 100644 --- a/cmd/vsql/in.v +++ b/cmd/vsql/in.v @@ -6,23 +6,23 @@ import vsql fn register_in_command(mut cmd cli.Command) { mut in_cmd := cli.Command{ - name: 'in' - usage: '' + name: 'in' + usage: '' required_args: 1 - description: 'Import schema and data' - execute: in_command + description: 'Import schema and data' + execute: in_command } in_cmd.add_flag(cli.Flag{ - flag: .bool - name: 'continue-on-error' + flag: .bool + name: 'continue-on-error' description: 'Continue when errors occur' }) in_cmd.add_flag(cli.Flag{ - flag: .bool - name: 'verbose' - abbrev: 'v' + flag: .bool + name: 'verbose' + abbrev: 'v' description: 'Show result of each command' }) diff --git a/cmd/vsql/main.v b/cmd/vsql/main.v index 1d5c087..23bd8db 100644 --- a/cmd/vsql/main.v +++ b/cmd/vsql/main.v @@ -5,9 +5,9 @@ import os fn main() { mut cmd := cli.Command{ - name: 'vsql' + name: 'vsql' description: 'vsql is a single-file or PostgeSQL-compatible SQL database written in V.\nhttps://github.com/elliotchance/vsql' - execute: unknown_command + execute: unknown_command } register_bench_command(mut cmd) diff --git a/cmd/vsql/out.v b/cmd/vsql/out.v index 738493a..53f1d9b 100644 --- a/cmd/vsql/out.v +++ b/cmd/vsql/out.v @@ -5,16 +5,16 @@ import vsql fn register_out_command(mut cmd cli.Command) { mut out_cmd := cli.Command{ - name: 'out' - usage: '' + name: 'out' + usage: '' required_args: 1 - description: 'Export schema and data' - execute: out_command + description: 'Export schema and data' + execute: out_command } out_cmd.add_flag(cli.Flag{ - flag: .bool - name: 'create-public-schema' + flag: .bool + name: 'create-public-schema' description: 'Include "CREATE SCHEMA PUBLIC"' }) diff --git a/cmd/vsql/server.v b/cmd/vsql/server.v index 232d59a..04ec58e 100644 --- a/cmd/vsql/server.v +++ b/cmd/vsql/server.v @@ -5,22 +5,22 @@ import vsql fn register_server_command(mut cmd cli.Command) { mut server_cmd := cli.Command{ - name: 'server' - description: 'Run as a server for PostgreSQL clients' - usage: '' + name: 'server' + description: 'Run as a server for PostgreSQL clients' + usage: '' required_args: 1 - execute: server_command + execute: server_command } server_cmd.add_flag(cli.Flag{ - flag: .bool - name: 'verbose' - abbrev: 'v' + flag: .bool + name: 'verbose' + abbrev: 'v' description: 'Verbose (show all messages in and out of the server)' }) server_cmd.add_flag(cli.Flag{ - flag: .int - name: 'port' - abbrev: 'p' + flag: .int + name: 'port' + abbrev: 'p' description: 'Port number (default 3210)' }) cmd.add_command(server_cmd) @@ -36,7 +36,7 @@ fn server_command(cmd cli.Command) ! { mut server := vsql.new_server(vsql.ServerOptions{ db_file: cmd.args[0] - port: port + port: port verbose: cmd.flags.get_bool('verbose') or { false } }) diff --git a/cmd/vsql/version.v b/cmd/vsql/version.v index 6b739b7..daf055d 100644 --- a/cmd/vsql/version.v +++ b/cmd/vsql/version.v @@ -4,9 +4,9 @@ import cli fn register_version_command(mut cmd cli.Command) { mut version_cmd := cli.Command{ - name: 'version' + name: 'version' description: 'Show version' - execute: version_command + execute: version_command } cmd.add_command(version_cmd) } diff --git a/docs/contributing.rst b/docs/contributing.rst index 047a266..2789d76 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -52,34 +52,16 @@ rebuild the entire docs you can use: Parser & SQL Grammar -------------------- -To make changes to the SQL grammar you will need to modify the ``grammar.bnf`` -file. These rules are partial or complete BNF rules from the +To make changes to the SQL grammar you will need to modify the ``*.y`` files. +These rules are partial or complete BNF rules from the `2016 SQL standard `_. -Within ``grammar.bnf`` you will see that some of the rules have a parser -function which is a name after ``->``. The actual parser function will have -``parse_`` prefix added. You can find all the existing parse functions in the -``parse.v`` file. - -If a rule does not have a parse function (no ``->``) then the value will be -passed up the chain which is the desired behavior in most cases. However, be -careful if there are multiple terms, you will need to provide a parse function -to return the correct term. - -Each of the rules can have an optional type described in ``/* */`` before -``::=``. Rules that do not have a type will be ignored as parameters for parse -functions. Otherwise, these types are used in the generated code to make sure -the correct types are passed into the parse functions. - -After making changes to ``grammar.bnf`` you will need to run: +After making changes to grammar file(s) you will need to run: .. code-block:: sh make grammar -Now, when running `v test .` you may receive errors for missing ``parse_`` -functions, you should implement those now. - Testing ------- diff --git a/docs/faq.rst b/docs/faq.rst index 791f822..7070898 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -44,15 +44,6 @@ available and these will be close enough to use as a reference: 1. `SQL 2016 Foundation Grammar (BNF) `_ 2. `SQL 1999 `_ -Wait! You said this is pure V but I see a Python file? ------------------------------------------------------- - -The python file ``generate-grammar.py`` is used to convert the BNF grammar into -V code that makes up the parser. The result file ``grammar.v`` is committed so -there is no need to use python to compile and otherwise work on vsql unless you -need to change the grammar. However, if you would like to covert this script to -V, I'd be happy to have your help! - Why is there no `NOW()` function? --------------------------------- diff --git a/docs/requirements.txt b/docs/requirements.txt index 483eb39..b471fc1 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,3 +1,3 @@ -sphinx==5.0.0 -sphinx_rtd_theme==1.0.0 +sphinx==8.0.0 +sphinx_rtd_theme==3.0.2 readthedocs-sphinx-search==0.1.1 diff --git a/docs/sql-compliance.rst b/docs/sql-compliance.rst index 6ce63fe..320244a 100644 --- a/docs/sql-compliance.rst +++ b/docs/sql-compliance.rst @@ -13,7 +13,7 @@ Mandatory Features ------------------ As of the latest version (or at least the version of this documentation) -**vsql supports 62 of the 164 mandatory features** of the +**vsql supports 65 of the 164 mandatory features** of the `SQL:2016 Standard `_. .. list-table:: Table 43 — Feature taxonomy and definition for mandatory features @@ -43,7 +43,7 @@ As of the latest version (or at least the version of this documentation) * - ✅ E011-06 - Implicit casting among the numeric data types - * - ⭕ **E021** + * - ✅ **E021** - **Character string types** * - ✅ E021-01 @@ -73,13 +73,13 @@ As of the latest version (or at least the version of this documentation) * - ✅ E021-09 - ``TRIM`` function - * - ❌ E021-10 + * - ✅ E021-10 - Implicit casting among the fixed-length and variable-length character string types * - ✅ E021-11 - ``POSITION`` function - * - ⭕ E021-12 + * - ✅ E021-12 - Character comparison * - ✅ **E031** diff --git a/docs/v-client-library-docs.rst b/docs/v-client-library-docs.rst index 1a95ebe..05e73a0 100644 --- a/docs/v-client-library-docs.rst +++ b/docs/v-client-library-docs.rst @@ -141,16 +141,6 @@ new_numeric_value expects a value to be valid and the size and scale are determi -fn new_query_cache ------------------- - - -.. code-block:: v - - pub fn new_query_cache() &QueryCache - -Create a new query cache. - fn new_real_value ----------------- @@ -393,17 +383,15 @@ struct Connection catalogs map[string]&CatalogConnection // funcs only needs to be initialized once on open() funcs []Func - // query_cache is maintained over file reopens. - query_cache &QueryCache // cast_rules are use for CAST() (see cast.v) cast_rules map[string]CastFunc // unary_operators and binary_operators are for operators (see operators.v) unary_operators map[string]UnaryOperatorFunc binary_operators map[string]BinaryOperatorFunc - // current_schema is where to search for unquailified table names. It will + // current_schema is where to search for unqualified table names. It will // have an initial value of 'PUBLIC'. current_schema string - // current_catalog (also known as the database). It will have an inital value + // current_catalog (also known as the database). It will have an initial value // derived from the first database file loaded. current_catalog string pub mut: @@ -431,14 +419,6 @@ struct ConnectionOptions pub struct ConnectionOptions { pub mut: - // query_cache contains the precompiled prepared statements that can be - // reused. This makes execution much faster as parsing the SQL is extremely - // expensive. - // - // By default each connection will be given its own query cache. However, - // you can safely share a single cache over multiple connections and you are - // encouraged to do so. - query_cache &QueryCache = unsafe { nil } // Warning: This only works for :memory: databases. Configuring it for // file-based databases will either be ignored or causes crashes. page_size int @@ -548,20 +528,6 @@ struct PreparedStmt A prepared statement is compiled and validated, but not executed. It can then be executed with a set of host parameters to be substituted into the statement. Each invocation requires all host parameters to be passed in. -struct QueryCache ------------------ - - -.. code-block:: v - - @[heap] - pub struct QueryCache { - mut: - stmts map[string]Stmt - } - -A QueryCache improves the performance of parsing by caching previously cached statements. By default, a new QueryCache is created for each Connection. However, you can share a single QueryCache safely amung multiple connections for even better performance. See ConnectionOptions. - struct Result ------------- diff --git a/examples/orm.v b/examples/orm.v new file mode 100644 index 0000000..4620714 --- /dev/null +++ b/examples/orm.v @@ -0,0 +1,61 @@ +import os +import vsql +import time + +fn main() { + os.rm('test.vsql') or {} + + example() or { panic(err) } +} + +// NOTE for some reason if we declare a @[primary] on a struct field, we can not do delete queries on the tables... +// https://github.com/elliotchance/vsql/issues/200 +struct Product { + id int //@[primary] + product_name string @[sql_type: 'varchar(100)'] + price f64 +} + +fn example() ! { + timer := time.new_stopwatch() + mut db := vsql.open('test.vsql')! + + sql db { + create table Product + }! + + products := [ + Product{1, 'Ice Cream', 5.99}, + Product{2, 'Ham Sandwhich', 3.47}, + Product{3, 'Bagel', 1.25}, + ] + + for product in products { + sql db { + insert product into Product + } or { panic(err) } + } + sql db { + update Product set product_name = 'Cereal' where id == 1 + } or { panic(err) } + + prod_one := sql db { + select from Product where id == 1 + }! + + assert prod_one.len == 1 + + sql db { + delete from Product where product_name == 'Cereal' + } or { panic(err) } + + all := sql db { + select from Product + }! + + assert all.len == 2 + + println(timer.elapsed()) + // println(typeof[?int]().idx) + // println(typeof[int]().idx) +} diff --git a/generate-grammar.py b/generate-grammar.py deleted file mode 100644 index e2b89a7..0000000 --- a/generate-grammar.py +++ /dev/null @@ -1,531 +0,0 @@ -import re -import sys - -# type RuleOrString = EarleyRule | string - -class EarleyProduction(object): - # terms []RuleOrString - - def __init__(self, terms): - assert isinstance(terms, list) - if len(terms) > 0: - assert isinstance(terms[0], str) or isinstance(terms[0], EarleyRule) - - self.terms = terms - - def index(self): - return ','.join([t.__str__() for t in self.terms]) - -class EarleyRule(object): - # name string - # productions []EarleyProduction - - def __init__(self, name, productions): - assert isinstance(name, str) - assert isinstance(productions, list) - if len(productions) > 0: - assert isinstance(productions[0], EarleyProduction) - - self.name = name - self.productions = productions - - def __str__(self): - return self.name - - # def __repr__(self): - # return "%s ::= %s" % (self.name, " | ".join(repr(p) for p in self.productions)) - -class EarleyState(object): - # name string - # production EarleyProduction - # dot_index int - # start_column EarleyColumn - # rules []EarleyRule - - def __init__(self, name, production, dot_index, start_column): - assert isinstance(name, str) - assert isinstance(production, EarleyProduction) - assert isinstance(dot_index, int) - assert isinstance(start_column, EarleyColumn) - - self.name = name - self.production = production - self.start_column = start_column - self.dot_index = dot_index - - rules = [] - for t in production.terms: - if isinstance(t, EarleyRule): - rules.append(t) - - self.rules = rules - - def __repr__(self): - terms = [] - for p in self.production.terms: - terms.append(p.__str__()) - - terms.insert(self.dot_index, "$") - - return f'{self.name} -> {" ".join(terms)} [{self.start_column}-{self.end_column}]' - - def eq(self, other): - return self.name == other.name and self.production == other.production \ - and self.dot_index == other.dot_index and self.start_column == other.start_column - - def index(self): - return f'{self.name} {self.production.index()} {self.dot_index} {self.start_column}' - - def completed(self): - return self.dot_index >= len(self.production.terms) - - def next_term(self): - if self.completed(): - return '' - - return self.production.terms[self.dot_index] - -class Set(object): - def __init__(self): - self.elems = {} - - def exists(self, v): - return v.index() in self.elems - - def add(self, v): - if self.exists(v): - return - - self.elems[v.index()] = v - -class EarleyColumn(object): - # index int - # token string - # value string - # states []EarleyState - # unique Set - - def __init__(self, index, token, value): - assert isinstance(index, int) - assert isinstance(token, str) - assert isinstance(value, str) - - self.index = index - self.token = token - self.value = value - self.states = [] - self.unique = Set() - - def __str__(self): - return f'{self.index}' - - def __repr__(self): - return f'{self.token}:{self.value}' - - def add(self, state): - if not self.unique.exists(state): - self.unique.add(state) - state.end_column = self - self.states.append(state) - return True - - return False - - def print(self, completedOnly = False): - print(f'[{self.index}] {self.token.__repr__()}') - print("=" * 35) - for s in self.states: - if completedOnly and not s.completed(): - continue - print(repr(s)) - print() - -class EarleyNode(object): - # value EarleyState - # children []EarleyNode - - def __init__(self, value, children): - assert isinstance(value, EarleyState) - assert isinstance(children, list) - if len(children) > 0: - assert isinstance(children[0], EarleyNode) - - self.value = value - self.children = children - - def print(self, level): - print(" " * level + str(self.value)) - for child in self.children: - child.print(level + 1) - - def max(self): - max = 0 - for child in self.children: - child_max = child.max() - if child_max > max: - max = child_max - - if self.value.end_column.index > max: - max = self.value.end_column.index - - return max - -def predict(col, rule): - assert isinstance(rule, EarleyRule) - - for prod in rule.productions: - col.add(EarleyState(rule.name, prod, 0, col)) - -def scan(col, state, token): - assert isinstance(state, EarleyState) - assert isinstance(token, str) - - if token != col.token: - return - - col.add(EarleyState(state.name, state.production, state.dot_index + 1, state.start_column)) - -def complete(col, state): - assert isinstance(state, EarleyState) - - if not state.completed(): - return - - for st in state.start_column.states: - term = st.next_term() - if not isinstance(term, EarleyRule): - continue - if term.name == state.name: - col.add(EarleyState(st.name, st.production, st.dot_index + 1, st.start_column)) - -def parse3(rule, table): - assert isinstance(rule, EarleyRule) - assert isinstance(table, list) - if len(table) > 0: - assert isinstance(table[0], EarleyColumn) - - table[0].add(EarleyState("start", EarleyProduction([rule]), 0, table[0])) - - for i, col in enumerate(table): - for j, state in enumerate(col.states): - if state.completed(): - complete(col, state) - else: - term = state.next_term() - if isinstance(term, EarleyRule): - predict(col, term) - elif i + 1 < len(table): - scan(table[i+1], state, term) - - # find gamma rule in last table column (otherwise fail) - for st in table[len(table)-1].states: - if st.name == "start" and st.completed(): - return st - - max = 0 - for col in table: - for st in col.states: - t = build_trees(st) - if len(t) > 0: - m = t[0].max() - if m > max: - max = m - - if max+1 >= len(table): - max = len(table)-2 - - raise ValueError("syntax error at " + table[max+1].value) - -def build_trees(state): - assert isinstance(state, EarleyState) - - return build_trees_helper([], state, len(state.rules) - 1, state.end_column) - -def build_trees_helper(children, state, rule_index, end_column): - assert isinstance(children, list) - if len(children) > 0: - assert isinstance(children[0], EarleyNode) - assert isinstance(state, EarleyState) - assert isinstance(rule_index, int) - assert isinstance(end_column, EarleyColumn) - - start_column = EarleyColumn(-1, '', '') - if rule_index < 0: - return [EarleyNode(state, children)] - elif rule_index == 0: - start_column = state.start_column - - rule = state.rules[rule_index] - outputs = [] - for st in end_column.states: - if st is state: - break - if st is state or not st.completed() or st.name != rule.name: - continue - if start_column.index >= 0 and st.start_column != start_column: - continue - for sub_tree in build_trees(st): - new_children = [sub_tree] - for child in children: - new_children.append(child) - - for node in build_trees_helper(new_children, state, rule_index - 1, st.start_column): - outputs.append(node) - - return outputs - -def parse_grammar(grammar): - grammar_rules = { - '^identifier': EarleyRule("^identifier", [EarleyProduction(["^identifier"])]), - '^integer': EarleyRule("^integer", [EarleyProduction(["^integer"])]), - '^string': EarleyRule("^string", [EarleyProduction(["^string"])]), - } - lines = [] - parse_functions = {} - grammar_types = {} - - full_line = '' - for line in grammar.split("\n"): - if line.startswith('#'): - continue - if '/*' in line and '::=' in line: - parts = line.split('/*') - full_line += parts[0] + "::= " - grammar_types[parts[0].strip()] = parts[1].split('*/')[0].strip() - elif line.strip() == '': - if full_line != '': - lines.append(full_line.strip()) - full_line = '' - else: - full_line += ' ' + line - - # First parse to collect empty rules by name - for line_number, line in enumerate(lines): - name, raw_rules = line.split(' ::=') - - if '"' in raw_rules: - grammar_rules[name] = EarleyRule(name, [EarleyProduction(re.findall('"(.+)"', raw_rules))]) - continue - - grammar_rule = EarleyRule(name, []) - grammar_rules[name] = grammar_rule - rules = raw_rules.split(' | ') - for rule_number, rule in enumerate(rules): - # If there is a parsing function we replace the current rule with a - # subrule: - # - # /* Term */ ::= - # - # | -> term_1 - # | -> term_2 - # - # Becomes: - # - # ::= - # - # | - # | - # - # ::= - # - # ::= - # - # The subrule is important becuase it creates a single node that we - # can capture all the children and invoke the parsing function. - if '->' in rule: - rule, parse_function = rule.split(' -> ') - new_rule = name[:-1] + ': #' + str(rule_number + 1) + '>' - lines.append(new_rule + ' ::= ' + rule) - rules[rule_number] = new_rule - parse_functions[new_rule] = (parse_function.strip(), re.findall('(<.*?>|[^\\s]+)', rule)) - - for token in re.findall('(<.*?>|[^\\s]+)', rule): - if token.isupper(): - grammar_rules[token] = EarleyRule(token, [EarleyProduction([token])]) - grammar_types[token] = '' - - lines[line_number] = name + ' ::= ' + ' | '.join(rules) - - grammar_rules[name] = grammar_rule - - # Second parse to add the productions for rules - for line in lines: - name, raw_rules = line.split(' ::= ') - - if '"' in raw_rules: - continue - - rules = raw_rules.split(' | ') - for rule in rules: - actual_rule = [] - for token in re.findall('(<.*?>|[^\\s]+)', rule): - actual_rule.append(grammar_rules[token]) - grammar_rules[name].productions.append(EarleyProduction(actual_rule)) - - return grammar_rules, parse_functions, grammar_types - -grammar_file = open('grammar.bnf', mode='r') -grammar, parse_functions, grammar_types = parse_grammar(grammar_file.read()) -grammar_file.close() - -def rule_name(name): - if isinstance(name, EarleyRule): - return name.name - - return name - -def var_name(name): - return "rule_" + rule_name(name).lower().replace('<', '').replace('#', '_').replace('-', '_').replace(':', '').replace('>', '_').replace(' ', '_').replace('^', '_') - -# Generate grammar file -grammar_file = open('vsql/grammar.v', mode='w') - -grammar_file.write('// grammar.v is generated. DO NOT EDIT.\n') -grammar_file.write('// It can be regenerated from the grammar.bnf with:\n') -grammar_file.write('// python generate-grammar.py\n\n') -grammar_file.write('module vsql\n\n') - -grammar_file.write('type EarleyValue = ') -grammar_file.write(' | '.join(sorted(set([g for g in grammar_types.values() if g])))) -grammar_file.write('\n\n') - -grammar_file.write('fn get_grammar() map[string]EarleyRule {\n') -grammar_file.write('\tmut rules := map[string]EarleyRule{}\n\n') - -for gr in sorted(grammar.keys(), key=lambda s: s.lower()): - grammar_file.write('\tmut ' + var_name(gr) + ' := &EarleyRule{name: "' + gr + '"}\n') - -grammar_file.write('\n') - -for gr in sorted(grammar.keys(), key=lambda s: s.lower()): - for production in grammar[gr].productions: - grammar_file.write('\t' + var_name(gr) + '.productions << &EarleyProduction{[\n') - for term in production.terms: - if isinstance(term, str): - grammar_file.write("\t\t&EarleyRuleOrString{str: '" + rule_name(term) + "', rule: unsafe { 0 }},\n") - else: - grammar_file.write("\t\t&EarleyRuleOrString{rule: " + var_name(term) + "},\n") - grammar_file.write('\t]}\n') - grammar_file.write('\n') - -for gr in sorted(grammar.keys(), key=lambda s: s.lower()): - grammar_file.write('\trules[\'' + rule_name(gr) + '\'] = ' + var_name(gr) + '\n') - -grammar_file.write('\n\treturn rules\n') -grammar_file.write('}\n\n') - -grammar_file.write("""fn parse_ast(node &EarleyNode) ![]EarleyValue { - if node.children.len == 0 { - match node.value.name { - '^integer' { - return [EarleyValue(node.value.end_column.value)] - } - '^identifier' { - return [EarleyValue(IdentifierChain{node.value.end_column.value})] - } - '^string' { - // See ISO/IEC 9075-2:2016(E), "5.3 ", #17 - return [EarleyValue(new_character_value(node.value.end_column.value))] - } - else { - if node.value.name[0] == `<` { - return [EarleyValue(node.value.end_column.value)] - } - - if node.value.name.is_upper() { - return [EarleyValue(node.value.name)] - } - - panic(node.value.name) - return []EarleyValue{} - } - } - } - - mut children := []EarleyValue{} - for child in node.children { - for result in parse_ast(child) ! { - children << result - } - } - - return parse_ast_name(children, node.value.name) -} - -fn parse_ast_name(children []EarleyValue, name string) ![]EarleyValue { - match name { -""") - -for rule in sorted(parse_functions.keys(), key=lambda s: s.lower()): - grammar_file.write("\t\t'" + rule + "' {\n") - function_name, terms = parse_functions[rule] - if function_name == function_name.lower(): - grammar_file.write("\t\t\treturn [EarleyValue(parse_" + function_name + "(") - else: - grammar_file.write("\t\t\treturn [EarleyValue(" + function_name + "(") - grammar_file.write(', '.join([ - 'children[' + str(i) + '] as ' + grammar_types[t] - for i, t in enumerate(terms) - if t in grammar_types and grammar_types[t] != ''])) - if function_name == function_name.lower(): - grammar_file.write(") !)]\n") - else: - grammar_file.write("))]\n") - - grammar_file.write("\t\t}\n") - -grammar_file.write("""\t\telse { - return children - } - } -} -""") - -grammar_file.close() - -def parse_tree(text): - tokens = [''] + text.split(' ') - table = [] - for i, token in enumerate(tokens): - if token == '(' or token == ')' or token == ',' or token == '.' or \ - token == '+' or token == '-' or token == '||' or token == ':': - table.append(EarleyColumn(i, token, token)) - elif token == '' or token.isupper(): - table.append(EarleyColumn(i, token, token)) - elif token[0] == "'": - table.append(EarleyColumn(i, '^string', token[1:-1])) - elif token.isdigit(): - table.append(EarleyColumn(i, '^integer', token)) - else: - table.append(EarleyColumn(i, '^identifier', token)) - - q0 = parse3(grammar[''], table) - build_trees(q0)[0].print(0) - -# Here are some examples you can enable for testing and debugging. - -# parse_tree("DROP TABLE foo") -# parse_tree("SELECT 1 + 2 FROM t") -# parse_tree("INSERT INTO foo ( a , b ) VALUES ( 123 , 'bar' )") -# parse_tree("CREATE TABLE foo ( x INT NOT NULL )") -# parse_tree("INSERT INTO t ( x ) VALUES ( 0 )") -# parse_tree("SELECT ABS ( 1 . 2 ) , ABS ( - 1 . 23 ) FROM t") -# parse_tree("SELECT FLOOR ( 3 . 7 ) , FLOOR ( 3 . 3 ) , FLOOR ( - 3 . 7 ) , FLOOR ( - 3 . 3 ) FROM t") -# parse_tree("CREATE TABLE ABS (x INT)") -# parse_tree("SELECT 'foo' || 'bar' AS Str FROM t") -# parse_tree("SELECT TRUE AND TRUE FROM t") -# parse_tree("SELECT * FROM t OFFSET 0 ROWS") -# parse_tree("SELECT * FROM t FETCH FIRST 1 ROW ONLY") -# parse_tree("SELECT product_name , no_pennies ( price ) AS total FROM products") -# parse_tree("SELECT * FROM ( VALUES 1 )") -# parse_tree("SELECT * FROM ( VALUES ROW ( 123 ) , ROW ( 456 ) )") -# parse_tree("SELECT x FROM ( SELECT y FROM t2 )") -# 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' )") -# parse_tree("CREATE SCHEMA public") - -for arg in sys.argv[1:]: - print(arg) - parse_tree(arg) diff --git a/scripts/generate-grammar.vsh b/scripts/generate-grammar.vsh new file mode 100755 index 0000000..9d00cd7 --- /dev/null +++ b/scripts/generate-grammar.vsh @@ -0,0 +1,40 @@ +#!/usr/bin/env -S v + +path := abs_path('vsql') +output_filename := join_path_single(path, 'y.y') +files := glob(join_path_single(path, '*.y')) or { [] } + +mut top := '' +mut middle := '' +mut bottom := '' + +for file_path in files { + if is_dir(file_path) { + continue + } + content := read_file(file_path) or { + eprintln('error: unable to open file ${file_path}: ${err}') + exit(1) + } + parts := content.split('%%') + if parts.len != 3 { + eprintln('${file_path}: wrong number of parts (${parts.len})') + exit(1) + } + top += parts[0] + middle += parts[1] + bottom += parts[2] +} + +mut output := open_file(output_filename, 'w') or { + eprintln('error: unable to open file to write') + exit(1) +} + +output.writeln(top.trim_space())! +output.writeln('%%')! +output.writeln(middle.trim_space())! +output.writeln('%%')! +output.write_string(bottom.trim_space())! +output.flush() +output.close() diff --git a/scripts/vyacc.v b/scripts/vyacc.v new file mode 100644 index 0000000..a9a72ae --- /dev/null +++ b/scripts/vyacc.v @@ -0,0 +1,3725 @@ +/* +Derived from Go's yacc tool +https://pkg.go.dev/golang.org/x/tools/cmd/goyacc + +Derived from Inferno's utils/iyacc/yacc.c +http://code.google.com/p/inferno-os/source/browse/utils/iyacc/yacc.c + +This copyright NOTICE applies to all files in this directory and +subdirectories, unless another copyright notice appears in a given +file or subdirectory. If you take substantial code from this software to use in +other programs, you must somehow include with it an appropriate +copyright notice that includes the copyright notice and the other +notices below. It is fine (and often tidier) to do that in a separate +file such as NOTICE, LICENCE or COPYING. + + Copyright © 1994-1999 Lucent Technologies Inc. All rights reserved. + Portions Copyright © 1995-1997 C H Forsyth (forsyth@terzarima.net) + Portions Copyright © 1997-1999 Vita Nuova Limited + Portions Copyright © 2000-2007 Vita Nuova Holdings Limited (www.vitanuova.com) + Portions Copyright © 2004,2006 Bruce Ellis + Portions Copyright © 2005-2007 C H Forsyth (forsyth@terzarima.net) + Revisions Copyright © 2000-2007 Lucent Technologies Inc. and others + Portions Copyright © 2009 The Go Authors. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +module main + +// yacc +// major difference is lack of stem ("y" variable) +// +import flag +import os +import strings +import encoding.utf8 + +// the following are adjustable +// according to memory size +const actsize = 240000 +const nstates = 16000 +const tempsize = 16000 + +const syminc = 50 // increase for non-term or term +const ruleinc = 50 // increase for max rule length y.prodptr[i] +const prodinc = 100 // increase for productions y.prodptr +const wsetinc = 50 // increase for working sets y.wsets +const stateinc = 200 // increase for states y.statemem + +const private = 0xE000 // unicode private use + +// relationships which must hold: +// tempsize >= NTERMS + NNONTERM + 1; +// tempsize >= nstates; +// + +const ntbase = 0o10000 +const errcode = 8190 +const acceptcode = 8191 +const yylexunk = 3 +const tokstart = 4 // index of first defined token + +// no, left, right, binary assoc. +const noasc = 0 +const lasc = 1 +const rasc = 2 +const basc = 3 + +// flags for state generation +const done = 0 +const mustdo = 1 +const mustlookahead = 2 + +// flags for a rule having an action, and being reduced +const actflag = 4 +const redflag = 8 + +// output parser flags +const yy_flag = -1000 + +// parse tokens +const identifier = private + 0 +const mark = private + 1 +const term = private + 2 +const left = private + 3 +const right = private + 4 +const binary = private + 5 +const prec = private + 6 +const lcurly = private + 7 +const identcolon = private + 8 +const number = private + 9 +const start = private + 10 +const typedef = private + 11 +const typename = private + 12 +const union = private + 13 +const error = private + 14 + +const max_int8 = 127 +const min_int8 = -128 +const max_int16 = 32767 +const min_int16 = -32768 +const max_int32 = 2147483647 +const min_int32 = -2147483648 +const max_uint8 = 255 +const max_uint16 = 65535 + +const endfile = 0 +const empty = 1 +const whoknows = 0 +const ok = 1 +const nomore = -1000 + +// macros for getting associativity and precedence levels +fn assoc(i int) int { + return i & 3 +} + +fn plevel(i int) int { + return (i >> 4) & 0o77 +} + +fn typ(i int) int { + return (i >> 10) & 0o77 +} + +// macros for setting associativity and precedence levels +fn setasc(i int, j int) int { + return i | j +} + +fn setplev(i int, j int) int { + unsafe { + return i | (j << 4) + } +} + +fn settype(i int, j int) int { + unsafe { + return i | (j << 10) + } +} + +struct Vyacc { +mut: + // I/O descriptors + finput os.File // Reader: input file + stderr os.File // Writer + ftable os.File // Writer: y.v file + fcode strings.Builder = strings.new_builder(0) // saved code + foutput ?os.File // Writer: y.output file + + fmt_imported bool // output file has recorded an import of "fmt" + + oflag string // -o [y.v] - y.v file + vflag string // -v [y.output] - y.output file + lflag bool // -l - disable line directives + prefix string // name prefix for identifiers, default yy + restflag []string + + // communication variables between various I/O routines + infile string // input file name + numbval int // value of an input number + tokname string // input token name, slop for runes and 0 + tokflag bool + + // storage of types + ntypes int // number of types defined + typeset map[int]string = map[int]string{} // pointers to type tags + + // token information + ntokens int // number of tokens + tokset []Symb + toklev []int // vector with the precedence of the terminals + + // nonterminal information + nnonter int = -1 // the number of nonterminals + nontrst []Symb + start int // start symbol + + // state information + nstate int // number of states + pstate []int = []int{len: nstates + 2} // index into y.statemem to the descriptions of the states + statemem []Item + tystate []int = []int{len: nstates} // contains type information about the states + tstates []int // states generated by terminal gotos + ntstates []int // states generated by nonterminal gotos + mstates []int = []int{len: nstates} // chain of overflows of term/nonterm generation lists + lastred int // number of last reduction of a state + defact []int = []int{len: nstates} // default actions of states + + // lookahead set information + nolook int // flag to turn off lookahead computations + tbitset int // size of lookahead sets + clset Lkset // temporary storage for lookahead computations + + // working set information + wsets []Wset + cwp int + + // storage for action table + amem []int // action table storage + memp int // next free action table position + indgo []int = []int{len: nstates} // index to the stored goto table + + // temporary vector, indexable by states, terms, or ntokens + temp1 []int = []int{len: tempsize} // temporary storage, indexed by terms + ntokens or states + lineno int = 1 // current input line number + fatfl int = 1 // if on, error is fatal + nerrors int // number of y.errors + + // assigned token type values + extval int + + // grammar rule information + nprod int = 1 // number of productions + prdptr [][]int // pointers to descriptions of productions + levprd []int // precedence levels for the productions + rlines []int // line number for this rule + + // statistics collection variables + zzgoent int + zzgobest int + zzacent int + zzexcp int + zzclose int + zzrrconf int + zzsrconf int + zzstate int + + // optimizer arrays + yypgo [][]int + optst [][]int + ggreed []int + pgo []int + + maxspr int // maximum spread of any entry + maxoff int // maximum offset into a array + maxa int + + // storage for information about the nonterminals + pres [][][]int // vector of pointers to productions yielding each nonterminal + pfirst []Lkset + pempty []int // vector of nonterminals nontrivially deriving e + + // random stuff picked out from between functions + indebug int // debugging flag for cpfir + pidebug int // debugging flag for putitem + gsdebug int // debugging flag for stagen + cldebug int // debugging flag for closure + pkdebug int // debugging flag for apack + g2debug int // debugging for go2gen + adb int = 1 // debugging for callopt + + errors []ParseError + + state_table []Row + + zznewstate int + + yaccpar string // will be processed version of yaccpartext: s/$\$/prefix/g + + peekline int + + resrv []Resrv = [ + Resrv{'binary', binary}, + Resrv{'left', left}, + Resrv{'nonassoc', binary}, + Resrv{'prec', prec}, + Resrv{'right', right}, + Resrv{'start', start}, + Resrv{'term', term}, + Resrv{'token', term}, + Resrv{'type', typedef}, + Resrv{'union', @union}, + Resrv{'struct', @union}, + Resrv{'error', error}, +] + + // utility routines + peekrune rune +} + +fn (mut y Vyacc) init() { + mut parser := flag.new_flag_parser(os.args) + y.oflag = parser.string('', `o`, 'y.v', 'parser output', flag.FlagConfig{}) + y.prefix = parser.string('', `p`, 'yy', 'name prefix to use in generated code', flag.FlagConfig{}) + y.vflag = parser.string('', `v`, 'y.output', 'create parsing tables', flag.FlagConfig{}) + y.lflag = parser.bool('', `l`, false, 'disable line directives', flag.FlagConfig{}) + y.restflag = (parser.finalize() or { panic(err) })[1..] +} + +const initialstacksize = 16 + +// structure declarations +type Lkset = []int + +struct Pitem { +mut: + prod []int + off int // offset within the production + first int // first term or non-term in item + prodno int // production number for sorting +} + +struct Item { +mut: + pitem Pitem + look Lkset +} + +struct Symb { +mut: + name string + noconst bool + value int +} + +struct Wset { +mut: + pitem Pitem + flag int + ws Lkset +} + +struct Resrv { + name string + value int +} + +struct ParseError { + lineno int + tokens []string + msg string +} + +struct Row { + actions []int + default_action int +} + +const eof = -1 + +fn main() { + mut y := Vyacc{} + + y.init() + y.setup()! // initialize and read productions + + y.tbitset = (y.ntokens + 32) / 32 + y.cpres() // make table of which productions yield a given nonterminal + y.cempty() // make a table of which nonterminals can match the empty string + y.cpfir()! // make a table of firsts of nonterminals + + y.stagen()! // generate the states + + y.yypgo = [][]int{len: y.nnonter + 1} + y.optst = [][]int{len: y.nstate} + y.output()! // write the states and the tables + y.go2out()! + + y.hideprod()! + y.summary() + + y.callopt()! + + y.others()! + + exit_(0) +} + +fn (mut y Vyacc) setup() ! { + mut j := 0 + mut ty := 0 + + y.stderr = os.stderr() + y.foutput = none + + if y.restflag.len != 1 { + y.usage() + } + if initialstacksize < 1 { + // never set so cannot happen + y.stderr.write_string('yacc: stack size too small\n')! + y.usage() + } + y.yaccpar = yaccpartext.replace('$\$', y.prefix) + y.openup()! + + y.ftable.write_string('// Code generated by vyacc ${os.args[1..].join(' ')}. DO NOT EDIT.\n')! + + y.defin(0, '\$end') + y.extval = private // tokens start in unicode 'private use' + y.defin(0, 'error') + y.defin(1, '\$accept') + y.defin(0, '\$unk') + mut i := 0 + + mut t := y.gettok() + + outer: for { + match t { + mark, endfile { + break outer + } + int(`;`) { + // Do nothing. + } + start { + t = y.gettok() + if t != identifier { + y.errorf('bad %start construction') + } + y.start = y.chfind(1, y.tokname) + } + error { + lno := y.lineno + mut tokens := []string{} + for { + t2 := y.gettok() + if t2 == `:` { + break + } + if t2 != identifier && t2 != identcolon { + y.errorf('bad syntax in %error') + } + tokens << y.tokname + if t2 == identcolon { + break + } + } + if y.gettok() != identifier { + y.errorf('bad syntax in %error') + } + y.errors << ParseError{lno, tokens, y.tokname} + } + typedef { + t = y.gettok() + if t != typename { + y.errorf('bad syntax in %type') + } + ty = y.numbval + for { + t = y.gettok() + match t { + identifier { + t = y.chfind(1, y.tokname) + if t < ntbase { + j = typ(y.toklev[t]) + if j != 0 && j != ty { + y.errorf('type redeclaration of token ${y.tokset[t].name}') + } else { + y.toklev[t] = settype(y.toklev[t], ty) + } + } else { + j = y.nontrst[t - ntbase].value + if j != 0 && j != ty { + y.errorf('type redeclaration of nonterminal ${y.nontrst[t - ntbase].name}') + } else { + y.nontrst[t - ntbase].value = ty + } + } + continue + } + int(`,`) { + continue + } + else {} + } + break + } + continue + } + @union { + y.cpyunion()! + } + left, binary, right, term { + // nonzero means new prec. and assoc. + lev := t - term + if lev != 0 { + i++ + } + ty = 0 + + // get identifiers so defined + t = y.gettok() + + // there is a type defined + if t == typename { + ty = y.numbval + t = y.gettok() + } + for { + match t { + int(`,`) { + t = y.gettok() + continue + } + int(`;`) { + // Do nothing. + } + identifier { + j = y.chfind(0, y.tokname) + if j >= ntbase { + y.errorf('${y.tokname} defined earlier as nonterminal') + } + if lev != 0 { + if assoc(y.toklev[j]) != 0 { + y.errorf('redeclaration of precedence of ${y.tokname}') + } + y.toklev[j] = setasc(y.toklev[j], lev) + y.toklev[j] = setplev(y.toklev[j], i) + } + if ty != 0 { + if typ(y.toklev[j]) != 0 { + y.errorf('redeclaration of type of ${y.tokname}') + } + y.toklev[j] = settype(y.toklev[j], ty) + } + t = y.gettok() + if t == number { + y.tokset[j].value = y.numbval + t = y.gettok() + } + + continue + } + else {} + } + break + } + continue + } + lcurly { + y.cpycode()! + } + else { + y.errorf('syntax error tok=${t - private}') + } + } + t = y.gettok() + } + + if t == endfile { + y.errorf('unexpected EOF before %') + } + + y.fcode.write_string('match ${y.prefix}nt {\n') + + y.moreprod() + y.prdptr[0] = [int(ntbase), y.start, 1, 0] + + y.nprod = 1 + mut curprod := []int{len: ruleinc} + t = y.gettok() + if t != identcolon { + y.errorf('bad syntax on first rule') + } + + if y.start == 0 { + y.prdptr[0][1] = y.chfind(1, y.tokname) + } + + // read rules + // put into y.prdptr array in the format + // target + // followed by id's of terminals and non-terminals + // followed by -y.nprod + + for t != mark && t != endfile { + mut mem := 0 + + // process a rule + y.rlines[y.nprod] = y.lineno + ruleline := y.lineno + if t == `|` { + curprod[mem] = y.prdptr[y.nprod - 1][0] + mem++ + } else if t == identcolon { + curprod[mem] = y.chfind(1, y.tokname) + if curprod[mem] < ntbase { + y.lerrorf(ruleline, 'token illegal on LHS of grammar rule') + } + mem++ + } else { + y.lerrorf(ruleline, 'illegal rule: missing semicolon or | ?') + } + + // read rule body + t = y.gettok() + for { + for t == identifier { + curprod[mem] = y.chfind(1, y.tokname) + if curprod[mem] < ntbase { + y.levprd[y.nprod] = y.toklev[curprod[mem]] + } + mem++ + if mem >= curprod.len { + mut ncurprod := []int{len: mem + ruleinc} + gocopy(mut ncurprod, curprod) + curprod = ncurprod.clone() + } + t = y.gettok() + } + if t == prec { + if y.gettok() != identifier { + y.lerrorf(ruleline, 'illegal %prec syntax') + } + j = y.chfind(2, y.tokname) + if j >= ntbase { + y.lerrorf(ruleline, 'nonterminal ${y.nontrst[j - ntbase].name} illegal after %prec') + } + y.levprd[y.nprod] = y.toklev[j] + t = y.gettok() + } + if t != `=` { + break + } + y.levprd[y.nprod] |= actflag + y.fcode.write_string('\n\t${y.nprod} {') + y.fcode.write_string('\n\t\t${y.prefix}_dollar = ${y.prefix}_s[${y.prefix}pt-${mem - 1}..${y.prefix}pt+1].clone()') + y.cpyact(curprod, mem)! + y.fcode.write_string('\n\t}') + + // action within rule... + t = y.gettok() + if t == identifier { + // make it a nonterminal + j = y.chfind(1, '$\$${y.nprod}') + + // + // the current rule will become rule number y.nprod+1 + // enter null production for action + // + y.prdptr[y.nprod] = []int{len: 2} + y.prdptr[y.nprod][0] = j + y.prdptr[y.nprod][1] = -y.nprod + + // update the production information + y.nprod++ + y.moreprod() + y.levprd[y.nprod] = y.levprd[y.nprod - 1] & ~actflag + y.levprd[y.nprod - 1] = actflag + y.rlines[y.nprod] = y.lineno + + // make the action appear in the original rule + curprod[mem] = j + mem++ + if mem >= curprod.len { + mut ncurprod := []int{len: mem + ruleinc} + gocopy(mut ncurprod, curprod) + curprod = ncurprod.clone() + } + } + } + + for t == `;` { + t = y.gettok() + } + curprod[mem] = -y.nprod + mem++ + + // check that default action is reasonable + if y.ntypes != 0 && (y.levprd[y.nprod] & actflag) == 0 + && y.nontrst[curprod[0] - ntbase].value != 0 { + // no explicit action, LHS has value + mut tempty := curprod[1] + if tempty < 0 { + y.lerrorf(ruleline, 'must return a value, since LHS has a type') + } + if tempty >= ntbase { + tempty = y.nontrst[tempty - ntbase].value + } else { + tempty = typ(y.toklev[tempty]) + } + if tempty != y.nontrst[curprod[0] - ntbase].value { + y.lerrorf(ruleline, 'default action causes potential type clash') + } + } + y.moreprod() + y.prdptr[y.nprod] = []int{len: mem} + gocopy(mut y.prdptr[y.nprod], curprod) + y.nprod++ + y.moreprod() + y.levprd[y.nprod] = 0 + } + y.fcode.write_string('\n\telse {}') + + if tempsize < y.ntokens + y.nnonter + 1 { + y.errorf('too many tokens (${y.ntokens}) or non-terminals (${y.nnonter})') + } + + // + // end of all rules + // dump out the prefix code + // + + y.fcode.write_string('\n\t}') + + // put out non-literal terminals + for i2 := tokstart; i2 <= y.ntokens; i2++ { + // non-literals + if !y.tokset[i2].noconst { + y.ftable.write_string('const token_${y.tokset[i2].name.to_lower()} = ${y.tokset[i2].value}\n')! + } + } + + // put out names of tokens + y.ftable.write_string('\n')! + y.ftable.write_string('const ${y.prefix}_toknames = [\n')! + for i2 := 1; i2 <= y.ntokens; i2++ { + y.ftable.write_string("\t\"${y.tokset[i2].name.replace('$', '\\$')}\",\n")! + } + y.ftable.write_string(']\n')! + + // put out names of states. + // commented out to avoid a huge table just for debugging. + // re-enable to have the names in the binary. + y.ftable.write_string('\n')! + y.ftable.write_string('const ${y.prefix}_statenames = [\n')! + for j = tokstart; j <= y.ntokens; j++ { + y.ftable.write_string("\t\"${y.tokset[j].name}\",\n")! + } + y.ftable.write_string(']\n')! + + y.ftable.write_string('\n')! + y.ftable.write_string('const ${y.prefix}_eof_code = 1\n')! + y.ftable.write_string('const ${y.prefix}_err_code = 2\n')! + y.ftable.write_string('const ${y.prefix}_initial_stack_size = ${initialstacksize}\n')! + + // + // copy any postfix code + // + if t == mark { + if !y.lflag { + y.ftable.write_string('\n//line ${y.infile}:${y.lineno}\n')! + } + for { + c := y.getrune(mut y.finput) + if c == eof { + break + } + y.ftable.write_string(c.str())! + } + } +} + +// allocate enough room to hold another production +fn (mut y Vyacc) moreprod() { + n := y.prdptr.len + if y.nprod >= n { + nn := n + prodinc + mut aprod := [][]int{len: nn} + mut alevprd := []int{len: nn} + mut arlines := []int{len: nn} + + gocopy(mut aprod, y.prdptr) + gocopy(mut alevprd, y.levprd) + gocopy(mut arlines, y.rlines) + + y.prdptr = aprod + y.levprd = alevprd + y.rlines = arlines + } +} + +// define s to be a terminal if nt==0 +// or a nonterminal if nt==1 +fn (mut y Vyacc) defin(nt int, s string) int { + mut val := 0 + if nt != 0 { + y.nnonter++ + if y.nnonter >= y.nontrst.len { + mut anontrst := []Symb{len: y.nnonter + syminc} + gocopy(mut anontrst, y.nontrst) + y.nontrst = anontrst + } + y.nontrst[y.nnonter] = Symb{ + name: s + } + return ntbase + y.nnonter + } + + // must be a token + y.ntokens++ + if y.ntokens >= y.tokset.len { + nn := y.ntokens + syminc + mut atokset := []Symb{len: nn} + mut atoklev := []int{len: nn} + + gocopy(mut atoklev, y.toklev) + gocopy(mut atokset, y.tokset) + + y.tokset = atokset + y.toklev = atoklev + } + y.tokset[y.ntokens].name = s + y.toklev[y.ntokens] = 0 + + // establish value for token + // single character literal + if s[0] == `'` || s[0] == `"` { + q := unquote(s) or { + y.errorf('invalid token: ${err}') + '' + } + rq := q.runes() + if rq.len != 1 { + y.errorf('character token too long: ${s}') + } + val = int(rq[0]) + if val == 0 { + y.errorf('token value 0 is illegal') + } + y.tokset[y.ntokens].noconst = true + } else { + val = y.extval + y.extval++ + if s[0] == `$` { + y.tokset[y.ntokens].noconst = true + } + } + + y.tokset[y.ntokens].value = val + return y.ntokens +} + +fn gocopy[T](mut dst []T, src []T) int { + mut min := dst.len + if src.len < min { + min = src.len + } + for i := 0; i < min; i++ { + dst[i] = src[i] + } + return src.len +} + +fn (mut y Vyacc) gettok() int { + mut i := 0 + mut mtch := rune(0) + mut c := rune(0) + + y.tokname = '' + for { + y.lineno += y.peekline + y.peekline = 0 + c = y.getrune(mut y.finput) + for c == ` ` || c == `\n` || c == `\t` || c == `\v` || c == `\r` { + if c == `\n` { + y.lineno++ + } + c = y.getrune(mut y.finput) + } + + // skip comment -- fix + if c != `/` { + break + } + y.lineno += y.skipcom() + } + + match c { + eof { + if y.tokflag { + print('>>> endfile ${y.lineno}\n') + } + return endfile + } + `{` { + y.ungetrune(y.finput, c) + if y.tokflag { + print('>>> ={ ${y.lineno}\n') + } + return int(`=`) + } + `<` { + // get, and look up, a type name (union member name) + c = y.getrune(mut y.finput) + for c != `>` && c != eof && c != `\n` { + y.tokname += c.str() + c = y.getrune(mut y.finput) + } + + if c != `>` { + y.errorf('unterminated < ... > clause') + } + + for i = 1; i <= y.ntypes; i++ { + if y.typeset[i] == y.tokname { + y.numbval = i + if y.tokflag { + print('>>> typename old <${y.tokname}> ${y.lineno}\n') + } + return typename + } + } + y.ntypes++ + y.numbval = y.ntypes + y.typeset[y.numbval] = y.tokname + if y.tokflag { + print('>>> typename new <${y.tokname}> ${y.lineno}\n') + } + return typename + } + `"`, `'` { + mtch = c + y.tokname = c.str() + for { + c = y.getrune(mut y.finput) + if c == `\n` || c == eof { + y.errorf('illegal or missing \' or "') + } + if c == `\\` { + y.tokname += `\\`.str() + c = y.getrune(mut y.finput) + } else if c == mtch { + if y.tokflag { + print(">>> identifier \"${y.tokname}\" ${y.lineno}\n") + } + y.tokname += c.str() + return identifier + } + y.tokname += c.str() + } + } + `%` { + c = y.getrune(mut y.finput) + match c { + `%` { + if y.tokflag { + print('>>> mark % ${y.lineno}\n') + } + return mark + } + `=` { + if y.tokflag { + print('>>> prec %= ${y.lineno}\n') + } + return prec + } + `{` { + if y.tokflag { + print('>>> lcurly %{ ${y.lineno}\n') + } + return lcurly + } + else {} + } + + y.getword(c) + // find a reserved word + for i2, _ in y.resrv { + if y.tokname == y.resrv[i2].name { + if y.tokflag { + print('>>> %${y.tokname} ${y.resrv[i2].value - private} ${y.lineno}\n') + } + return y.resrv[i2].value + } + } + y.errorf('invalid escape, or illegal reserved word: ${y.tokname}') + } + `0`, `1`, `2`, `3`, `4`, `5`, `6`, `7`, `8`, `9` { + y.numbval = int(c - `0`) + for { + c = y.getrune(mut y.finput) + if !isdigit(c) { + break + } + y.numbval = y.numbval * 10 + int(c - `0`) + } + y.ungetrune(y.finput, c) + if y.tokflag { + print('>>> number ${y.numbval} ${y.lineno}\n') + } + return number + } + else { + if isword(c) || c == `.` || c == `$` { + y.getword(c) + } else { + if y.tokflag { + print('>>> OPERATOR ${c.str()} ${y.lineno}\n') + } + return int(c) + } + } + } + + // look ahead to distinguish identifier from identcolon + c = y.getrune(mut y.finput) + for c == ` ` || c == `\t` || c == `\n` || c == `\v` || c == `\r` || c == `/` { + if c == `\n` { + y.peekline++ + } + // look for comments + if c == `/` { + y.peekline += y.skipcom() + } + c = y.getrune(mut y.finput) + } + if c == `:` { + if y.tokflag { + print('>>> identcolon ${y.tokname}: ${y.lineno}\n') + } + return identcolon + } + + y.ungetrune(y.finput, c) + if y.tokflag { + print('>>> identifier ${y.tokname} ${y.lineno}\n') + } + return identifier +} + +fn (mut y Vyacc) getword(c rune) { + mut c2 := c + y.tokname = '' + for isword(c2) || isdigit(c2) || c2 == `.` || c2 == `$` { + y.tokname += c2.str() + c2 = y.getrune(mut y.finput) + } + y.ungetrune(y.finput, c2) +} + +// determine the type of a symbol +fn (mut y Vyacc) fdtype(t int) int { + mut v := 0 + mut s := '' + + if t >= ntbase { + v = y.nontrst[t - ntbase].value + s = y.nontrst[t - ntbase].name + } else { + v = typ(y.toklev[t]) + s = y.tokset[t].name + } + if v <= 0 { + y.errorf('must specify type for ${s}') + } + return v +} + +fn (mut y Vyacc) chfind(t int, s string) int { + mut t2 := t + + if s[0] == `"` || s[0] == `'` { + t2 = 0 + } + for i := 0; i <= y.ntokens; i++ { + if s == y.tokset[i].name { + return i + } + } + for i := 0; i <= y.nnonter; i++ { + if s == y.nontrst[i].name { + return ntbase + i + } + } + + // cannot find name + if t2 > 1 { + y.errorf('${s} should have been defined earlier') + } + return y.defin(t2, s) +} + +// copy the union declaration to the output, and the define file if present +fn (mut y Vyacc) cpyunion() ! { + if !y.lflag { + y.ftable.write_string('\n//line ${y.infile}:${y.lineno}\n')! + } + y.ftable.write_string('type ${y.prefix}SymType struct')! + + mut level := 0 + + out: for { + c := y.getrune(mut y.finput) + if c == eof { + y.errorf('EOF encountered while processing %union') + } + y.ftable.write(c.bytes())! + match c { + `\n` { + y.lineno++ + } + `{` { + if level == 0 { + y.ftable.write_string('\n\tyys int')! + } + level++ + } + `}` { + level-- + if level == 0 { + break out + } + } + else {} + } + } + y.ftable.write_string('\n\n')! +} + +// saves code between %{ and %} +// adds an import for __fmt__ the first time +fn (mut y Vyacc) cpycode() ! { + lno := y.lineno + + mut c := y.getrune(mut y.finput) + if c == `\n` { + c = y.getrune(mut y.finput) + y.lineno++ + } + if !y.lflag { + y.ftable.write_string('\n//line ${y.infile}:${y.lineno}\n')! + } + // accumulate until %} + mut code := []rune{cap: 1024} + for c != eof { + if c == `%` { + c = y.getrune(mut y.finput) + if c == `}` { + y.emitcode(code, lno + 1)! + return + } + code << `%` + } + code << c + if c == `\n` { + y.lineno++ + } + c = y.getrune(mut y.finput) + } + y.lineno = lno + y.errorf('eof before %}') +} + +// emits code saved up from between %{ and %} +// called by cpycode +// adds an import for __yyfmt__ after the package clause +fn (mut y Vyacc) emitcode(code []rune, lineno int) ! { + for i, line in lines(code) { + y.writecode(line)! + if !y.fmt_imported && is_package_clause(line) { + y.ftable.write_string('import __yyfmt__ "fmt"\n')! + if !y.lflag { + y.ftable.write_string('//line ${y.infile}:${lineno + i}\n\t\t')! + } + y.fmt_imported = true + } + } +} + +// does this line look like a package clause? not perfect: might be confused by early comments. +fn is_package_clause(line_ []rune) bool { + mut line := line_.clone() + line = skipspace(line) + + // must be big enough. + if line.len < 'package X\n'.len { + return false + } + + // must start with "package" + for i, r in 'package'.runes() { + if line[i] != r { + return false + } + } + line = skipspace(line['package'.len..]) + + // must have another identifier. + if line.len == 0 || (!utf8.is_letter(line[0]) && line[0] != `_`) { + return false + } + for line.len > 0 { + if !utf8.is_letter(line[0]) && !utf8.is_number(line[0]) && line[0] != `_` { + break + } + line = line.clone()[1..] + } + line = skipspace(line) + + // eol, newline, or comment must follow + if line.len == 0 { + return true + } + if line[0] == `\r` || line[0] == `\n` { + return true + } + if line.len >= 2 { + return line[0] == `/` && (line[1] == `/` || line[1] == `*`) + } + return false +} + +// skip initial spaces +fn skipspace(line_ []rune) []rune { + mut line := line_.clone() + for line.len > 0 { + if line[0] != ` ` && line[0] != `\t` { + break + } + line = line.clone()[1..] + } + return line +} + +// break code into lines +fn lines(code_ []rune) [][]rune { + mut code := code_.clone() + mut l := [][]rune{cap: 100} + for code.len > 0 { + // one line per loop + mut i := 0 + for idx, _ in code { + if code[idx] == `\n` { + i = idx + break + } + } + l << code[..i + 1] + code = code.clone()[i + 1..] + } + return l +} + +// writes code to y.ftable +fn (mut y Vyacc) writecode(code []rune) ! { + for _, r in code { + y.ftable.write_string(r.str())! + } +} + +// skip over comments +// skipcom is called after reading a `/` +fn (mut y Vyacc) skipcom() int { + mut c := y.getrune(mut y.finput) + if c == `/` { + for c != eof { + if c == `\n` { + return 1 + } + c = y.getrune(mut y.finput) + } + y.errorf('EOF inside comment') + return 0 + } + if c != `*` { + y.errorf('illegal comment') + } + + mut nl := 0 // lines skipped + c = y.getrune(mut y.finput) + + l1: + match c { + `*` { + c = y.getrune(mut y.finput) + if c != `/` { + unsafe { + goto l1 + } + } + } + `\n` { + nl++ + c = y.getrune(mut y.finput) + unsafe { + goto l1 + } + } + else { + c = y.getrune(mut y.finput) + unsafe { + goto l1 + } + } + } + return nl +} + +// copy action to the next ; or closing } +fn (mut y Vyacc) cpyact(curprod []int, max int) ! { + if !y.lflag { + y.fcode.write_string('\n//line ${y.infile}:${y.lineno}') + } + y.fcode.write_string('\n\t\t') + + lno := y.lineno + mut brac := 0 + + loop: for { + mut c := y.getrune(mut y.finput) + + match c { + `;` { + if brac == 0 { + y.fcode.write_string(c.str()) + return + } + } + `{` { + brac++ + } + `$` { + mut s := 1 + mut tok := -1 + c = y.getrune(mut y.finput) + + // type description + if c == `<` { + y.ungetrune(y.finput, c) + if y.gettok() != typename { + y.errorf('bad syntax on $ clause') + } + tok = y.numbval + c = y.getrune(mut y.finput) + } + if c == `$` { + y.fcode.write_string('${y.prefix}_val') + + // put out the proper tag... + if y.ntypes != 0 { + if tok < 0 { + tok = y.fdtype(curprod[0]) + } + y.fcode.write_string('.${y.typeset[tok]}') + } + continue loop + } + if c == `-` { + s = -s + c = y.getrune(mut y.finput) + } + mut j := 0 + if isdigit(c) { + for isdigit(c) { + j = j * 10 + int(c - `0`) + c = y.getrune(mut y.finput) + } + y.ungetrune(y.finput, c) + j = j * s + if j >= max { + y.errorf('Illegal use of $${j}') + } + } else if isword(c) || c == `.` { + // look for $name + y.ungetrune(y.finput, c) + if y.gettok() != identifier { + y.errorf('$ must be followed by an identifier') + } + tokn := y.chfind(2, y.tokname) + mut fnd := -1 + c = y.getrune(mut y.finput) + if c != `@` { + y.ungetrune(y.finput, c) + } else if y.gettok() != number { + y.errorf('@ must be followed by number') + } else { + fnd = y.numbval + } + for k := 1; k < max; k++ { + if tokn == curprod[k] { + fnd-- + if fnd <= 0 { + break + } + } + } + if j >= max { + y.errorf('\$name or \$name@number not found') + } + } else { + y.fcode.write_string('$') + if s < 0 { + y.fcode.write_string('-') + } + y.ungetrune(y.finput, c) + continue loop + } + y.fcode.write_string('${y.prefix}_dollar[${j}]') + + // put out the proper tag + if y.ntypes != 0 { + if j <= 0 && tok < 0 { + y.errorf('must specify type of $${j}') + } + if tok < 0 { + tok = y.fdtype(curprod[j]) + } + y.fcode.write_string('.${y.typeset[tok]}') + } + continue loop + } + `}` { + brac-- + if brac == 0 { + y.fcode.write_string(c.str()) + return + } + } + `/` { + nc := y.getrune(mut y.finput) + if nc != `/` && nc != `*` { + y.ungetrune(y.finput, nc) + } else { + // a comment + y.fcode.write_string(c.str()) + y.fcode.write_string(nc.str()) + c = y.getrune(mut y.finput) + for c != eof { + match true { + c == `\n` { + y.lineno++ + if nc == `/` { // end of // comment + unsafe { + goto swt + } + } + } + c == `*` && nc == `*` { // end of /* comment? + nnc := y.getrune(mut y.finput) + if nnc == `/` { + y.fcode.write_string('*') + y.fcode.write_string('/') + continue loop + } + y.ungetrune(y.finput, nnc) + } + else {} + } + y.fcode.write_string(c.str()) + c = y.getrune(mut y.finput) + } + y.errorf('EOF inside comment') + } + } + `'`, `"` { + // character string or constant + mtch := c + y.fcode.write_string(c.str()) + c = y.getrune(mut y.finput) + for c != eof { + if c == `\\` { + y.fcode.write_string(c.str()) + c = y.getrune(mut y.finput) + if c == `\n` { + y.lineno++ + } + } else if c == mtch { + unsafe { + goto swt + } + } + if c == `\n` { + y.errorf('newline in string or char const') + } + y.fcode.write_string(c.str()) + c = y.getrune(mut y.finput) + } + y.errorf('EOF in string or character constant') + } + eof { + y.lineno = lno + y.errorf('action does not terminate') + } + `\n` { + y.fcode.write_string('\n\t') + y.lineno++ + continue loop + } + else {} + } + swt: + y.fcode.write_string(c.str()) + } +} + +fn (mut y Vyacc) openup() ! { + y.infile = y.restflag[0] + y.finput = y.open(y.infile)! + + y.foutput = none + if y.vflag != '' { + y.foutput = y.create(y.vflag)! + } + + if y.oflag == '' { + y.oflag = 'y.v' + } + y.ftable = y.create(y.oflag)! +} + +// return a pointer to the name of symbol i +fn (y Vyacc) symnam(i int) string { + mut s := '' + + if i >= ntbase { + s = y.nontrst[i - ntbase].name + } else { + s = y.tokset[i].name + } + return s +} + +// set elements 0 through n-1 to c +fn aryfil(mut v []int, n int, c int) { + for i := 0; i < n; i++ { + v[i] = c + } +} + +// compute an array with the beginnings of productions yielding given nonterminals +// The array y.pres points to these lists +// the array pyield has the lists: the total size is only NPROD+1 +fn (mut y Vyacc) cpres() { + y.pres = [][][]int{len: y.nnonter + 1} + mut curres := [][]int{len: y.nprod} + + if false { + for j := 0; j <= y.nnonter; j++ { + print('y.nnonter[${j}] = ${y.nontrst[j].name}\n') + } + for j := 0; j < y.nprod; j++ { + print('y.prdptr[${j}][0] = ${y.prdptr[j][0] - ntbase}+ntbase\n') + } + } + + y.fatfl = 0 // make undefined symbols nonfatal + for i := 0; i <= y.nnonter; i++ { + mut n := 0 + c := i + ntbase + for j := 0; j < y.nprod; j++ { + if y.prdptr[j][0] == c { + curres[n] = y.prdptr[j][1..] + n++ + } + } + if n == 0 { + y.errorf('nonterminal ${y.nontrst[i].name} not defined') + continue + } + y.pres[i] = [][]int{len: n} + gocopy(mut y.pres[i], curres) + } + y.fatfl = 1 + if y.nerrors != 0 { + y.summary() + exit_(1) + } +} + +// mark nonterminals which derive the empty string +// also, look for nonterminals which don't derive any token strings +fn (mut y Vyacc) cempty() { + mut i := 0 + mut p := 0 + mut np := 0 + mut prd := []int{} + + y.pempty = []int{len: y.nnonter + 1} + + // first, use the array y.pempty to detect productions that can never be reduced + // set y.pempty to WHONOWS + aryfil(mut y.pempty, y.nnonter + 1, whoknows) + + // now, look at productions, marking nonterminals which derive something + + more: for { + for i = 0; i < y.nprod; i++ { + prd = y.prdptr[i] + if y.pempty[prd[0] - ntbase] != 0 { + continue + } + np = prd.len - 1 + for p = 1; p < np; p++ { + if prd[p] >= ntbase && y.pempty[prd[p] - ntbase] == whoknows { + break + } + } + // production can be derived + if p == np { + y.pempty[prd[0] - ntbase] = ok + continue more + } + } + break + } + + // now, look at the nonterminals, to see if they are all ok + for i = 0; i <= y.nnonter; i++ { + // the added production rises or falls as the start symbol ... + if i == 0 { + continue + } + if y.pempty[i] != ok { + y.fatfl = 0 + y.errorf('nonterminal ${y.nontrst[i].name} never derives any token string') + } + } + + if y.nerrors != 0 { + y.summary() + exit_(1) + } + + // now, compute the y.pempty array, to see which nonterminals derive the empty string + // set y.pempty to whoknows + aryfil(mut y.pempty, y.nnonter + 1, whoknows) + + // loop as long as we keep finding empty nonterminals + + again: for { + next: for i = 1; i < y.nprod; i++ { + // not known to be empty + prd = y.prdptr[i] + if y.pempty[prd[0] - ntbase] != whoknows { + continue + } + np = prd.len - 1 + for p = 1; p < np; p++ { + if prd[p] < ntbase || y.pempty[prd[p] - ntbase] != empty { + continue next + } + } + + // we have a nontrivially empty nonterminal + y.pempty[prd[0] - ntbase] = empty + + // got one ... try for another + continue again + } + return + } +} + +// compute an array with the first of nonterminals +fn (mut y Vyacc) cpfir() ! { + mut s := 0 + mut n := 0 + mut p := 0 + mut np := 0 + mut ch := 0 + mut i := 0 + mut curres := [][]int{} + mut prd := []int{} + + y.wsets = []Wset{len: y.nnonter + wsetinc} + y.pfirst = []Lkset{len: y.nnonter + 1} + for i = 0; i <= y.nnonter; i++ { + y.wsets[i].ws = y.mkset() + y.pfirst[i] = y.mkset() + curres = y.pres[i] + n = curres.len + + // initially fill the sets + for s = 0; s < n; s++ { + prd = curres[s] + np = prd.len - 1 + for p = 0; p < np; p++ { + ch = prd[p] + if ch < ntbase { + setbit(mut y.pfirst[i], ch) + break + } + if y.pempty[ch - ntbase] == 0 { + break + } + } + } + } + + // now, reflect transitivity + mut changes := 1 + for changes != 0 { + changes = 0 + for i = 0; i <= y.nnonter; i++ { + curres = y.pres[i] + n = curres.len + for s = 0; s < n; s++ { + prd = curres[s] + np = prd.len - 1 + for p = 0; p < np; p++ { + ch = prd[p] - ntbase + if ch < 0 { + break + } + changes |= y.setunion(mut y.pfirst[i], y.pfirst[ch]) + if y.pempty[ch] == 0 { + break + } + } + } + } + } + + if y.indebug == 0 { + return + } + if y.foutput != none { + for i = 0; i <= y.nnonter; i++ { + y.foutput.write_string('\n${y.nontrst[i].name}: ${y.pfirst[i]} ${y.pempty[i]}\n')! + } + } +} + +// generate the states +fn (mut y Vyacc) stagen() ! { + // initialize + y.nstate = 0 + y.tstates = []int{len: y.ntokens + 1} // states generated by terminal gotos + y.ntstates = []int{len: y.nnonter + 1} // states generated by nonterminal gotos + y.amem = []int{len: actsize} + y.memp = 0 + + y.clset = y.mkset() + y.pstate[0] = 0 + y.pstate[1] = 0 + aryfil(mut y.clset, y.tbitset, 0) + mut item := Pitem{y.prdptr[0], 0, 0, 0} + y.putitem(item, y.clset)! + y.tystate[0] = mustdo + y.nstate = 1 + y.pstate[2] = y.pstate[1] + + // + // now, the main state generation loop + // first pass generates all of the states + // later passes fix up lookahead + // could be sped up a lot by remembering + // results of the first pass rather than recomputing + // + mut first := 1 + for more := 1; more != 0; first = 0 { + more = 0 + for i := 0; i < y.nstate; i++ { + if y.tystate[i] != mustdo { + continue + } + + y.tystate[i] = done + aryfil(mut y.temp1, y.nnonter + 1, 0) + + // take state i, close it, and do gotos + y.closure(i)! + + // generate goto's + for p := 0; p < y.cwp; p++ { + pi := y.wsets[p] + if pi.flag != 0 { + continue + } + y.wsets[p].flag = 1 + c := pi.pitem.first + if c <= 1 { + if y.pstate[i + 1] - y.pstate[i] <= p { + y.tystate[i] = mustlookahead + } + continue + } + + // do a goto on c + y.putitem(y.wsets[p].pitem, y.wsets[p].ws)! + for q := p + 1; q < y.cwp; q++ { + // this item contributes to the goto + if c == y.wsets[q].pitem.first { + y.putitem(y.wsets[q].pitem, y.wsets[q].ws)! + y.wsets[q].flag = 1 + } + } + + if c < ntbase { + y.state(c) // register new state + } else { + y.temp1[c - ntbase] = y.state(c) + } + } + + if y.gsdebug != 0 && y.foutput != none { + y.foutput.write_string('${i}: ')! + for j := 0; j <= y.nnonter; j++ { + if y.temp1[j] != 0 { + y.foutput.write_string('${y.nontrst[j].name} ${y.temp1[j]},')! + } + } + y.foutput.write_string('\n')! + } + + if first != 0 { + y.indgo[i] = y.apack(mut y.temp1[1..], y.nnonter - 1)! - 1 + } + + more++ + } + } +} + +// generate the closure of state i +fn (mut y Vyacc) closure(i int) ! { + y.zzclose++ + + // first, copy kernel of state i to y.wsets + y.cwp = 0 + q := y.pstate[i + 1] + for p := y.pstate[i]; p < q; p++ { + y.wsets[y.cwp].pitem = y.statemem[p].pitem + y.wsets[y.cwp].flag = 1 // this item must get closed + gocopy(mut y.wsets[y.cwp].ws, y.statemem[p].look) + y.cwp++ + } + + // now, go through the loop, closing each item + mut work := 1 + for work != 0 { + work = 0 + for u := 0; u < y.cwp; u++ { + if y.wsets[u].flag == 0 { + continue + } + + // dot is before c + c := y.wsets[u].pitem.first + if c < ntbase { + y.wsets[u].flag = 0 + // only interesting case is where . is before nonterminal + continue + } + + // compute the lookahead + aryfil(mut y.clset, y.tbitset, 0) + + // find items involving c + for v := u; v < y.cwp; v++ { + if y.wsets[v].flag != 1 || y.wsets[v].pitem.first != c { + continue + } + pi := y.wsets[v].pitem.prod + mut ipi := y.wsets[v].pitem.off + 1 + + y.wsets[v].flag = 0 + if y.nolook != 0 { + continue + } + + mut ch := pi[ipi] + ipi++ + for ch > 0 { + // terminal symbol + if ch < ntbase { + setbit(mut y.clset, ch) + break + } + + // nonterminal symbol + y.setunion(mut y.clset, y.pfirst[ch - ntbase]) + if y.pempty[ch - ntbase] == 0 { + break + } + ch = pi[ipi] + ipi++ + } + if ch <= 0 { + y.setunion(mut y.clset, y.wsets[v].ws) + } + } + + // + // now loop over productions derived from c + // + curres := y.pres[c - ntbase] + n := curres.len + + nexts: + // initially fill the sets + for s := 0; s < n; s++ { + prd := curres[s] + + // + // put these items into the closure + // is the item there + // + for v := 0; v < y.cwp; v++ { + // yes, it is there + if y.wsets[v].pitem.off == 0 && aryeq(y.wsets[v].pitem.prod, prd) != 0 { + if y.nolook == 0 && y.setunion(mut y.wsets[v].ws, y.clset) != 0 { + y.wsets[v].flag = 1 + work = 1 + } + continue nexts + } + } + + // not there; make a new entry + if y.cwp >= y.wsets.len { + mut awsets := []Wset{len: y.cwp + wsetinc} + gocopy(mut awsets, y.wsets) + y.wsets = awsets + } + y.wsets[y.cwp].pitem = Pitem{prd, 0, prd[0], -prd[prd.len - 1]} + y.wsets[y.cwp].flag = 1 + y.wsets[y.cwp].ws = y.mkset() + if y.nolook == 0 { + work = 1 + gocopy(mut y.wsets[y.cwp].ws, y.clset) + } + y.cwp++ + } + } + } + + // have computed closure; flags are reset; return + if y.cldebug != 0 && y.foutput != none { + y.foutput.write_string('\nState ${i}, nolook = ${y.nolook}\n')! + for u := 0; u < y.cwp; u++ { + if y.wsets[u].flag != 0 { + y.foutput.write_string('flag set\n')! + } + y.wsets[u].flag = 0 + y.foutput.write_string('\t${y.writem(y.wsets[u].pitem)}')! + y.prlook(y.wsets[u].ws)! + y.foutput.write_string('\n')! + } + } +} + +// sorts last state,and sees if it equals earlier ones. returns state number +fn (mut y Vyacc) state(c int) int { + y.zzstate++ + p1 := y.pstate[y.nstate] + p2 := y.pstate[y.nstate + 1] + if p1 == p2 { + return 0 // null state + } + + // sort the items + mut k := 0 + mut l := 0 + for k = p1 + 1; k < p2; k++ { // make k the biggest + for l = k; l > p1; l-- { + if y.statemem[l].pitem.prodno < y.statemem[l - 1].pitem.prodno + || (y.statemem[l].pitem.prodno == y.statemem[l - 1].pitem.prodno + && y.statemem[l].pitem.off < y.statemem[l - 1].pitem.off) { + s := y.statemem[l] + y.statemem[l] = y.statemem[l - 1] + y.statemem[l - 1] = s + } else { + break + } + } + } + + size1 := p2 - p1 // size of state + + mut i := 0 + if c >= ntbase { + i = y.ntstates[c - ntbase] + } else { + i = y.tstates[c] + } + + look: for ; i != 0; i = y.mstates[i] { + // get ith state + q1 := y.pstate[i] + q2 := y.pstate[i + 1] + size2 := q2 - q1 + if size1 != size2 { + continue + } + k = p1 + for l = q1; l < q2; l++ { + if aryeq(y.statemem[l].pitem.prod, y.statemem[k].pitem.prod) == 0 + || y.statemem[l].pitem.off != y.statemem[k].pitem.off { + continue look + } + k++ + } + + // found it + y.pstate[y.nstate + 1] = y.pstate[y.nstate] // delete last state + + // fix up lookaheads + if y.nolook != 0 { + return i + } + k = p1 + for l = q1; l < q2; l++ { + if y.setunion(mut y.statemem[l].look, y.statemem[k].look) != 0 { + y.tystate[i] = mustdo + } + k++ + } + return i + } + + // state is new + y.zznewstate++ + if y.nolook != 0 { + y.errorf('yacc state/y.nolook error') + } + y.pstate[y.nstate + 2] = p2 + if y.nstate + 1 >= nstates { + y.errorf('too many states') + } + if c >= ntbase { + y.mstates[y.nstate] = y.ntstates[c - ntbase] + y.ntstates[c - ntbase] = y.nstate + } else { + y.mstates[y.nstate] = y.tstates[c] + y.tstates[c] = y.nstate + } + y.tystate[y.nstate] = mustdo + y.nstate++ + return y.nstate - 1 +} + +fn (mut y Vyacc) putitem(p_ Pitem, set Lkset) ! { + mut p := p_ + p.off++ + p.first = p.prod[p.off] + + if y.pidebug != 0 && y.foutput != none { + y.foutput.write_string('putitem(${y.writem(p)}), state ${y.nstate}\n')! + } + mut j := y.pstate[y.nstate + 1] + if j >= y.statemem.len { + mut asmm := []Item{len: j + stateinc} + gocopy(mut asmm, y.statemem) + y.statemem = asmm + } + y.statemem[j].pitem = p + if y.nolook == 0 { + mut s := y.mkset() + gocopy(mut s, set) + y.statemem[j].look = s + } + j++ + y.pstate[y.nstate + 1] = j +} + +// creates output string for item pointed to by pp +fn (mut y Vyacc) writem(pp Pitem) string { + mut i := 0 + + mut p := pp.prod.clone() + mut q := chcopy(y.nontrst[y.prdptr[pp.prodno][0] - ntbase].name) + ': ' + mut npi := pp.off + + mut pi := aryeq(p, y.prdptr[pp.prodno]) + + for { + mut c := ` ` + if pi == npi { + c = `.` + } + q += c.str() + + i = p[pi] + pi++ + if i <= 0 { + break + } + q += chcopy(y.symnam(i)) + } + + // an item calling for a reduction + i = p[npi] + if i < 0 { + q += ' (${-i})' + } + + return q +} + +// pack state i from y.temp1 into y.amem +fn (mut y Vyacc) apack(mut p []int, n_ int) !int { + mut n := n_ + // + // we don't need to worry about checking because + // we will only look at entries known to be there... + // eliminate leading and trailing 0's + // + mut off := 0 + mut pp := 0 + for ; pp <= n && p[pp] == 0; pp++ { + off-- + } + + // no actions + if pp > n { + return 0 + } + for ; n > pp && p[n] == 0; n-- { + } + p = p.clone()[pp..n + 1] + + // now, find a place for the elements from p to q, inclusive + r := y.amem.len - p.len + + nextk: for rr := 0; rr <= r; rr++ { + mut qq := rr + for pp = 0; pp < p.len; pp++ { + if p[pp] != 0 { + if p[pp] != y.amem[qq] && y.amem[qq] != 0 { + continue nextk + } + } + qq++ + } + + // we have found an acceptable k + if y.pkdebug != 0 && y.foutput != none { + y.foutput.write_string('off = ${off + rr}, k = ${rr}\n')! + } + qq = rr + for pp = 0; pp < p.len; pp++ { + if p[pp] != 0 { + if qq > y.memp { + y.memp = qq + } + y.amem[qq] = p[pp] + } + qq++ + } + if y.pkdebug != 0 && y.foutput != none { + for pp = 0; pp <= y.memp; pp += 10 { + y.foutput.write_string('\n')! + for qq = pp; qq <= pp + 9; qq++ { + y.foutput.write_string('${y.amem[qq]} ')! + } + y.foutput.write_string('\n')! + } + } + return off + rr + } + y.errorf('no space in action table') + return 0 +} + +// print the output for the states +fn (mut y Vyacc) output() ! { + mut c := 0 + mut u := 0 + mut v := 0 + + if !y.lflag { + y.ftable.write_string('\n//line yacctab:1')! + } + mut actions := []int{} + + if y.errors.len > 0 { + y.state_table = []Row{len: y.nstate} + } + + noset := y.mkset() + + // output the stuff for state i + for i := 0; i < y.nstate; i++ { + y.nolook = 0 + if y.tystate[i] != mustlookahead { + y.nolook = 1 + } + y.closure(i)! + + // output actions + y.nolook = 1 + aryfil(mut y.temp1, y.ntokens + y.nnonter + 1, 0) + for u = 0; u < y.cwp; u++ { + c = y.wsets[u].pitem.first + if c > 1 && c < ntbase && y.temp1[c] == 0 { + for v = u; v < y.cwp; v++ { + if c == y.wsets[v].pitem.first { + y.putitem(y.wsets[v].pitem, noset)! + } + } + y.temp1[c] = y.state(c) + } else if c > ntbase { + c -= ntbase + if y.temp1[c + y.ntokens] == 0 { + y.temp1[c + y.ntokens] = y.amem[y.indgo[i] + c] + } + } + } + if i == 1 { + y.temp1[1] = acceptcode + } + + // now, we have the shifts; look at the reductions + y.lastred = 0 + for u = 0; u < y.cwp; u++ { + c = y.wsets[u].pitem.first + + // reduction + if c > 0 { + continue + } + y.lastred = -c + us := y.wsets[u].ws + for k := 0; k <= y.ntokens; k++ { + if bitset(us, k) == 0 { + continue + } + if y.temp1[k] == 0 { + y.temp1[k] = c + } else if y.temp1[k] < 0 { // reduce/reduce conflict + if y.foutput != none { + y.foutput.write_string("\n ${i}: reduce/reduce conflict (red'ns " + + '${-y.temp1[k]} and ${y.lastred}) on ${y.symnam(k)}')! + } + if -y.temp1[k] > y.lastred { + y.temp1[k] = -y.lastred + } + y.zzrrconf++ + } else { + // potential shift/reduce conflict + y.precftn(y.lastred, k, i)! + } + } + } + actions = y.add_actions(mut actions, i)! + } + + y.array_out_columns('_exca', actions, 2, false)! + y.ftable.write_string('\n')! + y.ftable.write_string('\n')! + y.ftable.write_string('const ${y.prefix}_private = ${private}\n')! +} + +// decide a shift/reduce conflict by precedence. +// r is a rule number, t a token number +// the conflict is in state s +// y.temp1[t] is changed to reflect the action +fn (mut y Vyacc) precftn(r int, t int, s int) ! { + mut action := noasc + + mut lp := y.levprd[r] + mut lt := y.toklev[t] + if plevel(lt) == 0 || plevel(lp) == 0 { + // conflict + if y.foutput != none { + y.foutput.write_string("\n${s}: shift/reduce conflict (shift ${y.temp1[t]}(${plevel(lt)}), red'n ${r}(${plevel(lp)})) on ${y.symnam(t)}")! + } + y.zzsrconf++ + return + } + if plevel(lt) == plevel(lp) { + action = assoc(lt) + } else if plevel(lt) > plevel(lp) { + action = rasc // shift + } else { + action = lasc + } // reduce + match action { + basc { // error action + y.temp1[t] = errcode + } + lasc { // reduce + y.temp1[t] = -r + } + else {} + } +} + +// output state i +// y.temp1 has the actions, y.lastred the default +fn (mut y Vyacc) add_actions(mut act []int, i int) ![]int { + mut p := 0 + mut p1 := 0 + + // find the best choice for y.lastred + y.lastred = 0 + mut ntimes := 0 + for j := 0; j <= y.ntokens; j++ { + if y.temp1[j] >= 0 { + continue + } + if y.temp1[j] + y.lastred == 0 { + continue + } + // count the number of appearances of y.temp1[j] + mut count := 0 + tred := -y.temp1[j] + y.levprd[tred] |= redflag + for p = 0; p <= y.ntokens; p++ { + if y.temp1[p] + tred == 0 { + count++ + } + } + if count > ntimes { + y.lastred = tred + ntimes = count + } + } + + // + // for error recovery, arrange that, if there is a shift on the + // error recovery token, `error', that the default be the error action + // + if y.temp1[2] > 0 { + y.lastred = 0 + } + + // clear out entries in y.temp1 which equal y.lastred + // count entries in y.optst table + mut n := 0 + for p = 0; p <= y.ntokens; p++ { + p1 = y.temp1[p] + if p1 + y.lastred == 0 { + y.temp1[p] = 0 + p1 = 0 + } + if p1 > 0 && p1 != acceptcode && p1 != errcode { + n++ + } + } + + y.wrstate(i)! + y.defact[i] = y.lastred + mut f := 0 + mut os_ := []int{len: n * 2} + n = 0 + for p = 0; p <= y.ntokens; p++ { + p1 = y.temp1[p] + if p1 != 0 { + if p1 < 0 { + p1 = -p1 + } else if p1 == acceptcode { + p1 = -1 + } else if p1 == errcode { + p1 = 0 + } else { + os_[n] = p + n++ + os_[n] = p1 + n++ + y.zzacent++ + continue + } + if f == 0 { + act << -1 + act << i + } + f++ + act << p + act << p1 + y.zzexcp++ + } + } + + if f != 0 { + y.defact[i] = -2 + act << -2 + act << y.lastred + } + y.optst[i] = os_ + + return act +} + +// writes state i +fn (mut y Vyacc) wrstate(i int) ! { + mut j0 := 0 + mut j1 := 0 + mut u := 0 + mut pp := 0 + mut qq := 0 + + if y.errors.len > 0 { + actions := y.temp1.clone() + mut default_action := errcode + if y.lastred != 0 { + default_action = -y.lastred + } + y.state_table[i] = Row{actions, default_action} + } + + if y.foutput != none { + y.foutput.write_string('\nstate ${i}\n')! + qq = y.pstate[i + 1] + for pp = y.pstate[i]; pp < qq; pp++ { + y.foutput.write_string('\t${y.writem(y.statemem[pp].pitem)}\n')! + } + if y.tystate[i] == mustlookahead { + // print out empty productions in closure + for u = y.pstate[i + 1] - y.pstate[i]; u < y.cwp; u++ { + if y.wsets[u].pitem.first < 0 { + y.foutput.write_string('\t${y.writem(y.wsets[u].pitem)}\n')! + } + } + } + + // check for state equal to another + for j0 = 0; j0 <= y.ntokens; j0++ { + j1 = y.temp1[j0] + if j1 != 0 { + y.foutput.write_string('\n\t${y.symnam(j0)} ')! + + // shift, error, or accept + if j1 > 0 { + if j1 == acceptcode { + y.foutput.write_string('accept')! + } else if j1 == errcode { + y.foutput.write_string('error')! + } else { + y.foutput.write_string('shift ${j1}')! + } + } else { + y.foutput.write_string('reduce ${-j1} (src line ${y.rlines[-j1]})')! + } + } + } + + // output the final production + if y.lastred != 0 { + y.foutput.write_string('\n\t. reduce ${y.lastred} (src line ${y.rlines[y.lastred]})\n\n')! + } else { + y.foutput.write_string('\n\t. error\n\n')! + } + + // now, output nonterminal actions + j1 = y.ntokens + for j0 = 1; j0 <= y.nnonter; j0++ { + j1++ + if y.temp1[j1] != 0 { + y.foutput.write_string('\t${y.symnam(j0 + ntbase)} goto ${y.temp1[j1]}\n')! + } + } + } +} + +// output the gotos for the nontermninals +fn (mut y Vyacc) go2out() ! { + for i := 1; i <= y.nnonter; i++ { + y.go2gen(i)! + + // find the best one to make default + mut best := -1 + mut times := 0 + + // is j the most frequent + for j := 0; j < y.nstate; j++ { + if y.tystate[j] == 0 { + continue + } + if y.tystate[j] == best { + continue + } + + // is y.tystate[j] the most frequent + mut count := 0 + cbest := y.tystate[j] + for k := j; k < y.nstate; k++ { + if y.tystate[k] == cbest { + count++ + } + } + if count > times { + best = cbest + times = count + } + } + + // best is now the default entry + y.zzgobest += times - 1 + mut n := 0 + for j := 0; j < y.nstate; j++ { + if y.tystate[j] != 0 && y.tystate[j] != best { + n++ + } + } + mut goent := []int{len: 2 * n + 1} + n = 0 + for j := 0; j < y.nstate; j++ { + if y.tystate[j] != 0 && y.tystate[j] != best { + goent[n] = j + n++ + goent[n] = y.tystate[j] + n++ + y.zzgoent++ + } + } + + // now, the default + if best == -1 { + best = 0 + } + + y.zzgoent++ + goent[n] = best + y.yypgo[i] = goent + } +} + +// output the gotos for nonterminal c +fn (mut y Vyacc) go2gen(c int) ! { + mut i := 0 + mut cc := 0 + mut p := 0 + mut q := 0 + + // first, find nonterminals with gotos on c + aryfil(mut y.temp1, y.nnonter + 1, 0) + y.temp1[c] = 1 + mut work := 1 + for work != 0 { + work = 0 + for i = 0; i < y.nprod; i++ { + // cc is a nonterminal with a goto on c + cc = y.prdptr[i][1] - ntbase + if cc >= 0 && y.temp1[cc] != 0 { + // thus, the left side of production i does too + cc = y.prdptr[i][0] - ntbase + if y.temp1[cc] == 0 { + work = 1 + y.temp1[cc] = 1 + } + } + } + } + + // now, we have y.temp1[c] = 1 if a goto on c in closure of cc + if y.g2debug != 0 && y.foutput != none { + y.foutput.write_string('${y.nontrst[c].name}: gotos on ')! + for i = 0; i <= y.nnonter; i++ { + if y.temp1[i] != 0 { + y.foutput.write_string('${y.nontrst[i].name} ')! + } + } + y.foutput.write_string('\n')! + } + + // now, go through and put gotos into y.tystate + aryfil(mut y.tystate, y.nstate, 0) + for i = 0; i < y.nstate; i++ { + q = y.pstate[i + 1] + for p = y.pstate[i]; p < q; p++ { + cc = y.statemem[p].pitem.first + if cc >= ntbase { + // goto on c is possible + if y.temp1[cc - ntbase] != 0 { + y.tystate[i] = y.amem[y.indgo[i] + c] + break + } + } + } + } +} + +// in order to free up the mem and y.amem arrays for the optimizer, +// and still be able to output yyr1, etc., after the sizes of +// the action array is known, we hide the nonterminals +// derived by productions in y.levprd. +fn (mut y Vyacc) hideprod() ! { + mut nred := 0 + y.levprd[0] = 0 + for i := 1; i < y.nprod; i++ { + if (y.levprd[i] & redflag) == 0 { + if y.foutput != none { + y.foutput.write_string('Rule not reduced: ${y.writem(Pitem{y.prdptr[i], 0, 0, i})}\n')! + } + print('rule ${y.writem(Pitem{y.prdptr[i], 0, 0, i})} never reduced\n') + nred++ + } + y.levprd[i] = y.prdptr[i][0] - ntbase + } + if nred != 0 { + print('${nred} rules never reduced\n') + } +} + +fn (mut y Vyacc) callopt() ! { + mut j := 0 + mut k := 0 + mut p := 0 + mut q := 0 + mut i := 0 + mut v := []int{} + + y.pgo = []int{len: y.nnonter + 1} + y.pgo[0] = 0 + y.maxoff = 0 + y.maxspr = 0 + for i = 0; i < y.nstate; i++ { + k = 32000 + j = 0 + v = y.optst[i] + q = v.len + for p = 0; p < q; p += 2 { + if v[p] > j { + j = v[p] + } + if v[p] < k { + k = v[p] + } + } + + // nontrivial situation + if k <= j { + // j is now the range + // j -= k; // call scj + if k > y.maxoff { + y.maxoff = k + } + } + y.tystate[i] = q + 2 * j + if j > y.maxspr { + y.maxspr = j + } + } + + // initialize y.ggreed table + y.ggreed = []int{len: y.nnonter + 1} + for i = 1; i <= y.nnonter; i++ { + y.ggreed[i] = 1 + j = 0 + + // minimum entry index is always 0 + v = y.yypgo[i] + q = v.len - 1 + for p = 0; p < q; p += 2 { + y.ggreed[i] += 2 + if v[p] > j { + j = v[p] + } + } + y.ggreed[i] = y.ggreed[i] + 2 * j + if j > y.maxoff { + y.maxoff = j + } + } + + // now, prepare to put the shift actions into the y.amem array + for i = 0; i < actsize; i++ { + y.amem[i] = 0 + } + y.maxa = 0 + for i = 0; i < y.nstate; i++ { + if y.tystate[i] == 0 && y.adb > 1 { + y.ftable.write_string('State ${i}: null\n')! + } + y.indgo[i] = yy_flag + } + + i = y.nxti() + for i != nomore { + if i >= 0 { + y.stin(i)! + } else { + y.gin(-i)! + } + i = y.nxti() + } + + // print y.amem array + if y.adb > 2 { + for p = 0; p <= y.maxa; p += 10 { + y.ftable.write_string('${p} ')! + for i = 0; i < 10; i++ { + y.ftable.write_string('${y.amem[p + i]} ')! + } + y.ftable.write_string('\n')! + } + } + + y.aoutput()! + y.osummary()! +} + +// finds the next i +fn (mut y Vyacc) nxti() int { + mut max := 0 + mut maxi := 0 + for i := 1; i <= y.nnonter; i++ { + if y.ggreed[i] >= max { + max = y.ggreed[i] + maxi = -i + } + } + for i := 0; i < y.nstate; i++ { + if y.tystate[i] >= max { + max = y.tystate[i] + maxi = i + } + } + if max == 0 { + return nomore + } + return maxi +} + +fn (mut y Vyacc) gin(i int) ! { + mut s := 0 + + // enter gotos on nonterminal i into array y.amem + y.ggreed[i] = 0 + + q := y.yypgo[i] + nq := q.len - 1 + + // now, find y.amem place for it + + nextgp: for p := 0; p < actsize; p++ { + if y.amem[p] != 0 { + continue + } + for r := 0; r < nq; r += 2 { + s = p + q[r] + 1 + if s > y.maxa { + y.maxa = s + if y.maxa >= actsize { + y.errorf('a array overflow') + } + } + if y.amem[s] != 0 { + continue nextgp + } + } + + // we have found y.amem spot + y.amem[p] = q[nq] + if p > y.maxa { + y.maxa = p + } + for r := 0; r < nq; r += 2 { + s = p + q[r] + 1 + y.amem[s] = q[r + 1] + } + y.pgo[i] = p + if y.adb > 1 { + y.ftable.write_string('Nonterminal ${i}, entry at ${y.pgo[i]}\n')! + } + return + } + y.errorf('cannot place goto ${i}\n') +} + +fn (mut y Vyacc) stin(i int) ! { + mut s := 0 + + y.tystate[i] = 0 + + // enter state i into the y.amem array + q := y.optst[i] + nq := q.len + + nextn: + // find an acceptable place + for n := -y.maxoff; n < actsize; n++ { + mut f := 0 + for r := 0; r < nq; r += 2 { + s = q[r] + n + if s < 0 || s > actsize { + continue nextn + } + if y.amem[s] == 0 { + f++ + } else if y.amem[s] != q[r + 1] { + continue nextn + } + } + + // check the position equals another only if the states are identical + for j := 0; j < y.nstate; j++ { + if y.indgo[j] == n { + // we have some disagreement + if f != 0 { + continue nextn + } + if nq == y.optst[j].len { + // states are equal + y.indgo[i] = n + if y.adb > 1 { + y.ftable.write_string('State ${i}: entry at' + '${n} equals state ${j}\n')! + } + return + } + + // we have some disagreement + continue nextn + } + } + + for r := 0; r < nq; r += 2 { + s = q[r] + n + if s > y.maxa { + y.maxa = s + } + if y.amem[s] != 0 && y.amem[s] != q[r + 1] { + y.errorf("clobber of a array, pos'n ${s}, by ${q[r + 1]}") + } + y.amem[s] = q[r + 1] + } + y.indgo[i] = n + if y.adb > 1 { + y.ftable.write_string('State ${i}: entry at ${y.indgo[i]}\n')! + } + return + } + y.errorf('Error; failure to place state ${i}') +} + +// this version is for limbo +// write out the optimized parser +fn (mut y Vyacc) aoutput() ! { + y.ftable.write_string('\n')! + y.ftable.write_string('const ${y.prefix}_last = ${y.maxa + 1}\n')! + y.arout('_act', y.amem, y.maxa + 1)! + y.arout('_pact', y.indgo, y.nstate)! + y.arout('_pgo', y.pgo, y.nnonter + 1)! +} + +// put out other arrays, copy the parsers +fn (mut y Vyacc) others() ! { + mut i := 0 + mut j := 0 + + y.arout('_r1', y.levprd, y.nprod)! + aryfil(mut y.temp1, y.nprod, 0) + + // + // yyr2 is the number of rules for each production + // + for i = 1; i < y.nprod; i++ { + y.temp1[i] = y.prdptr[i].len - 2 + } + y.arout('_r2', y.temp1, y.nprod)! + + aryfil(mut y.temp1, y.nstate, -1000) + for i = 0; i <= y.ntokens; i++ { + for j2 := y.tstates[i]; j2 != 0; j2 = y.mstates[j2] { + y.temp1[j2] = i + } + } + for i = 0; i <= y.nnonter; i++ { + for j = y.ntstates[i]; j != 0; j = y.mstates[j] { + y.temp1[j] = -i + } + } + y.arout('_chk', y.temp1, y.nstate)! + y.array_out_columns('_def', y.defact[..y.nstate], 10, false)! + + // put out token translation tables + // table 1 has 0-256 + aryfil(mut y.temp1, 256, 0) + mut c := 0 + for i2 := 1; i2 <= y.ntokens; i2++ { + j = y.tokset[i2].value + if j >= 0 && j < 256 { + if y.temp1[j] != 0 { + print('yacc bug -- cannot have 2 different Ts with same value\n') + print(' ${y.tokset[i2].name} and ${y.tokset[y.temp1[j]].name}\n') + y.nerrors++ + } + y.temp1[j] = i2 + if j > c { + c = j + } + } + } + for i = 0; i <= c; i++ { + if y.temp1[i] == 0 { + y.temp1[i] = yylexunk + } + } + y.arout('_tok1', y.temp1, c + 1)! + + // table 2 has private-private+256 + aryfil(mut y.temp1, 256, 0) + c = 0 + for i = 1; i <= y.ntokens; i++ { + j = y.tokset[i].value - private + if j >= 0 && j < 256 { + if y.temp1[j] != 0 { + print('yacc bug -- cannot have 2 different Ts with same value\n') + print(' ${y.tokset[i].name} and ${y.tokset[y.temp1[j]].name}\n') + y.nerrors++ + } + y.temp1[j] = i + if j > c { + c = j + } + } + } + y.arout('_tok2', y.temp1, c + 1)! + + // table 3 has everything else + y.ftable.write_string('\n')! + mut v := []int{} + for i = 1; i <= y.ntokens; i++ { + j = y.tokset[i].value + if j >= 0 && j < 256 { + continue + } + if j >= private && j < 256 + private { + continue + } + + v << j + v << i + } + v << 0 + y.arout('_tok3', v, v.len)! + y.ftable.write_string('\n')! + + // Custom error messages. + y.ftable.write_string('\n')! + y.ftable.write_string('struct ErrorMessage {\n')! + y.ftable.write_string('\tstate int\n')! + y.ftable.write_string('\ttoken int\n')! + y.ftable.write_string('\tmsg string\n')! + y.ftable.write_string('}\n\n')! + if y.errors.len == 0 { + y.ftable.write_string('const ${y.prefix}_error_messages = []ErrorMessage{}\n')! + } else { + y.ftable.write_string('const ${y.prefix}_error_messages = [\n')! + for _, err in y.errors { + y.lineno = err.lineno + state, token := y.run_machine(err.tokens) + y.ftable.write_string('\tErrorMessage{${state}, ${token}, ${err.msg}},\n')! + } + y.ftable.write_string(']\n')! + } + + // copy parser text + mut ch := y.getrune(mut y.finput) + for ch != eof { + y.ftable.write_string(ch.str())! + ch = y.getrune(mut y.finput) + } + + // copy yaccpar + if !y.lflag { + y.ftable.write_string('\n//line yaccpar:1\n')! + } + + parts := y.yaccpar.split_n(y.prefix + 'run()', 2) + y.ftable.write_string('${parts[0]}')! + y.ftable.write_string(y.fcode.str())! + y.ftable.write_string('${parts[1]}')! +} + +fn (mut y Vyacc) run_machine(tokens []string) (int, int) { + mut state := 0 + mut token := 0 + mut stack := []int{} + mut i := 0 + token = -1 + + Loop: + if token < 0 { + token = y.chfind(2, tokens[i]) + i++ + } + + row := y.state_table[state] + + mut c := token + if token >= ntbase { + c = token - ntbase + y.ntokens + } + mut action := row.actions[c] + if action == 0 { + action = row.default_action + } + + match true { + action == acceptcode { + y.errorf('tokens are accepted') + return state, token + } + action == errcode { + if token >= ntbase { + y.errorf('error at non-terminal token ${y.symnam(token)}') + } + return state, token + } + action > 0 { + // Shift to state action. + stack << state + state = action + token = -1 + unsafe { + goto Loop + } + } + else { + // Reduce by production -action. + prod := y.prdptr[-action] + rhs_len := prod.len - 2 + if rhs_len > 0 { + n := stack.len - rhs_len + state = stack[n] + stack = stack.clone()[..n] + } + if token >= 0 { + i-- + } + token = prod[0] + unsafe { + goto Loop + } + } + } + + return state, token +} + +fn min_max(v []int) (int, int) { + mut min := 0 + mut max := 0 + if v.len == 0 { + return min, max + } + min = v[0] + max = v[0] + for _, i in v { + if i < min { + min = i + } + if i > max { + max = i + } + } + return min, max +} + +// return the smaller integral base type to store the values in v +fn min_type(v []int, allow_unsigned bool) string { + mut typ := 'int' + mut type_len := 8 + min, max := min_max(v) + + if min >= min_int32 && max <= max_int32 && type_len > 4 { + typ = 'int32' + type_len = 4 + } + if min >= min_int16 && max <= max_int16 && type_len > 2 { + typ = 'int16' + type_len = 2 + } + if min >= min_int8 && max <= max_int8 && type_len > 1 { + typ = 'int8' + type_len = 1 + } + + if allow_unsigned { + // Do not check for uint32, not worth and won't compile on 32 bit systems + + if min >= 0 && max <= max_uint16 && type_len > 2 { + typ = 'uint16' + type_len = 2 + } + if min >= 0 && max <= max_uint8 && type_len > 1 { + typ = 'uint8' + type_len = 1 + } + } + return typ +} + +fn (mut y Vyacc) array_out_columns(s_ string, v []int, columns int, allow_unsigned bool) ! { + mut s := y.prefix + s_ + y.ftable.write_string('\n')! + // min_typ := min_type(v, allow_unsigned) + y.ftable.write_string('const ${s} = [')! + for i, val in v { + if i % columns == 0 { + y.ftable.write_string('\n\t')! + } else { + y.ftable.write_string(' ')! + } + y.ftable.write_string('${val},')! + } + y.ftable.write_string('\n]\n')! +} + +fn (mut y Vyacc) arout(s string, v []int, n int) ! { + y.array_out_columns(s, v[..n], 10, true)! +} + +// output the summary on y.output +fn (mut y Vyacc) summary() { + if y.foutput != none { + y.foutput.write_string('\n${y.ntokens} terminals, ${y.nnonter + 1} nonterminals\n') or { + panic(err) + } + y.foutput.write_string('${y.nprod} grammar rules, ${y.nstate}/${nstates} states\n') or { + panic(err) + } + y.foutput.write_string('${y.zzsrconf} shift/reduce, ${y.zzrrconf} reduce/reduce conflicts reported\n') or { + panic(err) + } + y.foutput.write_string('${y.wsets.len} working sets used\n') or { panic(err) } + y.foutput.write_string('memory: parser ${y.memp}/${actsize}\n') or { panic(err) } + y.foutput.write_string('${y.zzclose - 2 * y.nstate} extra closures\n') or { panic(err) } + y.foutput.write_string('${y.zzacent} shift entries, ${y.zzexcp} exceptions\n') or { + panic(err) + } + y.foutput.write_string('${y.zzgoent} goto entries\n') or { panic(err) } + y.foutput.write_string('${y.zzgobest} entries saved by goto default\n') or { panic(err) } + } + if y.zzsrconf != 0 || y.zzrrconf != 0 { + print('\nconflicts: ') + if y.zzsrconf != 0 { + print('${y.zzsrconf} shift/reduce') + } + if y.zzsrconf != 0 && y.zzrrconf != 0 { + print(', ') + } + if y.zzrrconf != 0 { + print('${y.zzrrconf} reduce/reduce') + } + print('\n') + } +} + +// write optimizer summary +fn (mut y Vyacc) osummary() ! { + if y.foutput != none { + mut i := 0 + for p := y.maxa; p >= 0; p-- { + if y.amem[p] == 0 { + i++ + } + } + + y.foutput.write_string('Optimizer space used: output ${y.maxa + 1}/${actsize}\n')! + y.foutput.write_string('${y.maxa + 1} table entries, ${i} zero\n')! + y.foutput.write_string('maximum spread: ${y.maxspr}, maximum offset: ${y.maxoff}\n')! + } +} + +// copies and protects "'s in q +fn chcopy(q string) string { + mut s := '' + mut i := 0 + mut j := 0 + for i = 0; i < q.len; i++ { + if q[i] == `"` { + s += q[j..i] + '\\' + j = i + } + } + return s + q[j..i] +} + +fn (mut y Vyacc) usage() { + y.stderr.write_string('usage: yacc [-o output] [-v parsetable] input\n') or { panic(err) } + exit_(1) +} + +fn bitset(set Lkset, bit int) int { + return set[bit >> 5] & (1 << u32(bit & 31)) +} + +fn setbit(mut set Lkset, bit int) { + set[bit >> 5] |= (1 << u32(bit & 31)) +} + +fn (y Vyacc) mkset() Lkset { + return []int{len: y.tbitset} +} + +// set a to the union of a and b +// return 1 if b is not a subset of a, 0 otherwise +fn (mut y Vyacc) setunion(mut a []int, b []int) int { + mut sub := 0 + for i := 0; i < y.tbitset; i++ { + x := a[i] + y_ := x | b[i] + a[i] = y_ + if y_ != x { + sub = 1 + } + } + return sub +} + +fn (mut y Vyacc) prlook(p Lkset) ! { + if y.foutput != none { + // if p == none { + // y.foutput.write_string("\tNULL")! + // return + // } + y.foutput.write_string(' { ')! + for j := 0; j <= y.ntokens; j++ { + if bitset(p, j) != 0 { + y.foutput.write_string('${y.symnam(j)} ')! + } + } + y.foutput.write_string('}')! + } +} + +fn isdigit(c rune) bool { + return c >= `0` && c <= `9` +} + +fn isword(c rune) bool { + return c >= 0xa0 || c == `_` || (c >= `a` && c <= `z`) || (c >= `A` && c <= `Z`) +} + +// return 1 if 2 arrays are equal +// return 0 if not equal +fn aryeq(a []int, b []int) int { + n := a.len + if b.len != n { + return 0 + } + for ll := 0; ll < n; ll++ { + if a[ll] != b[ll] { + return 0 + } + } + return 1 +} + +fn (mut y Vyacc) getrune(mut f os.File) rune { + mut r := rune(0) + + if y.peekrune != 0 { + if y.peekrune == eof { + return eof + } + r = y.peekrune + y.peekrune = 0 + return r + } + + if f.eof() { + return eof + } + + mut buf := []u8{len: 1} + + f.read(mut buf) or { return eof } + return buf[0] +} + +fn (mut y Vyacc) ungetrune(f os.File, c rune) { + if f != y.finput { + panic('ungetc - not y.finput') + } + if y.peekrune != 0 { + panic('ungetc - 2nd unget') + } + y.peekrune = c +} + +fn (mut y Vyacc) open(s string) !os.File { + return os.open(s) +} + +fn (mut y Vyacc) create(s string) !os.File { + return os.create(s) +} + +// write out error comment +fn (mut y Vyacc) lerrorf(lineno int, s string) { + y.nerrors++ + y.stderr.write_string(s) or { panic(err) } + y.stderr.write_string(': ${y.infile}:${lineno}\n') or { panic(err) } + if y.fatfl != 0 { + y.summary() + exit_(1) + } +} + +fn (mut y Vyacc) errorf(s string) { + y.lerrorf(y.lineno, s) +} + +fn exit_(status int) { + // if y.ftable != none { + // y.ftable.flush() + // } + // if y.foutput != none { + // y.foutput.flush() + // } + // if y.stderr != none { + // y.stderr.flush() + // } + exit(status) +} + +fn unquote(s string) !string { + return s[1..s.len - 2] +} + +const yaccpartext = ' +// parser for yacc output + +const $\$_debug = 0 +const $\$_error_verbose = true + +interface YYLexer { +mut: + lex(mut lval YYSymType) int + error(s string)! +} + +interface YYParser { +mut: + parse(mut YYLexer) !int + lookahead() int +} + +struct YYParserImpl { +mut: + lval YYSymType + stack [$\$_initial_stack_size]YYSymType + char int +} + +fn (mut p YYParserImpl) lookahead() int { + return p.char +} + +fn $\$_new_parser() YYParser { + return YYParserImpl{} +} + +const $\$_flag = -1000 + +fn $\$_tokname(c int) string { + if c >= 1 && c-1 < $\$_toknames.len { + if $\$_toknames[c-1] != "" { + return $\$_toknames[c-1] + } + } + return "tok-\$c" +} + +fn $\$_statname(s int) string { + if s >= 0 && s < $\$_statenames.len { + if $\$_statenames[s] != "" { + return $\$_statenames[s] + } + } + return "state-\$s" +} + +const tokstart = 4 + +fn $\$_error_message(state int, look_ahead int) string { + if !$\$_error_verbose { + return "syntax error" + } + + for e in $\$_error_messages { + if e.state == state && e.token == look_ahead { + return "syntax error: " + e.msg + } + } + + mut res := "syntax error: unexpected " + $\$_tokname(look_ahead) + + // To match Bison, suggest at most four expected tokens. + mut expected := []int{cap: 4} + + // Look for shiftable tokens. + base := int($\$_pact[state]) + for tok := tokstart; tok-1 < $\$_toknames.len; tok++ { + n := base + tok + if n >= 0 && n < $\$_last && int($\$_chk[int($\$_act[n])]) == tok { + if expected.len == expected.cap { + return res + } + expected << tok + } + } + + if $\$_def[state] == -2 { + mut i := 0 + for $\$_exca[i] != -1 || int($\$_exca[i+1]) != state { + i += 2 + } + + // Look for tokens that we accept or reduce. + for i += 2; $\$_exca[i] >= 0; i += 2 { + tok := int($\$_exca[i]) + if tok < tokstart || $\$_exca[i+1] == 0 { + continue + } + if expected.len == expected.cap { + return res + } + expected << tok + } + + // If the default action is to accept or reduce, give up. + if $\$_exca[i+1] != 0 { + return res + } + } + + for i, tok in expected { + if i == 0 { + res += ", expecting " + } else { + res += " or " + } + res += $\$_tokname(tok) + } + return res +} + +fn $\$lex1(mut lex YYLexer, mut lval YYSymType) (int, int) { + mut token := 0 + mut ch := lex.lex(mut lval) + if ch <= 0 { + token = int($\$_tok1[0]) + unsafe { goto out } + } + if ch < $\$_tok1.len { + token = int($\$_tok1[ch]) + unsafe { goto out } + } + if ch >= $\$_private { + if ch < $\$_private+$\$_tok2.len { + token = int($\$_tok2[ch-$\$_private]) + unsafe { goto out } + } + } + for i := 0; i < $\$_tok3.len; i += 2 { + token = int($\$_tok3[i+0]) + if token == ch { + token = int($\$_tok3[i+1]) + unsafe { goto out } + } + } + +out: + if token == 0 { + token = int($\$_tok2[1]) // unknown char + } + if $\$_debug >= 3 { + println("lex \${$\$_tokname(token)}(\${u8(ch)})") + } + return ch, token +} + +fn $\$_parse(mut $\$lex YYLexer) !int { + mut parser := $\$_new_parser() + return parser.parse(mut $\$lex) +} + +fn (mut $\$rcvr YYParserImpl) parse(mut $\$lex YYLexer) !int { + mut $\$n := 0 + mut $\$_val := YYSymType{} + mut $\$_dollar := []YYSymType{} + _ = $\$_dollar // silence set and not used + mut $\$_s := $\$rcvr.stack[..] + + mut n_errs := 0 // number of errors + mut err_flag := 0 // error recovery flag + mut $\$state := 0 + $\$rcvr.char = -1 + mut $\$token := -1 // $\$rcvr.char translated into internal numbering + defer { + // Make sure we report no lookahead when not parsing. + $\$state = -1 + $\$rcvr.char = -1 + $\$token = -1 + } + mut $\$p := -1 + unsafe { goto $\$stack } + +ret0: + return 0 + +ret1: + return 1 + +$\$stack: + // put a state and value onto the stack + if $\$_debug >= 4 { + println("char \${$\$_tokname($\$token)} in \${$\$_statname($\$state)}") + } + + $\$p++ + if $\$p >= $\$_s.len { + mut nyys := []YYSymType{len: $\$_s.len*2} + gocopy(mut nyys, $\$_s) + $\$_s = nyys.clone() + } + $\$_s[$\$p] = $\$_val + $\$_s[$\$p].yys = $\$state + +$\$newstate: + $\$n = int($\$_pact[$\$state]) + if $\$n <= $\$_flag { + unsafe { + goto $\$default // simple state + } + } + if $\$rcvr.char < 0 { + $\$rcvr.char, $\$token = $\$lex1(mut $\$lex, mut $\$rcvr.lval) + } + $\$n += $\$token + if $\$n < 0 || $\$n >= $\$_last { + unsafe { goto $\$default } + } + $\$n = int($\$_act[$\$n]) + if int($\$_chk[$\$n]) == $\$token { + // valid shift + $\$rcvr.char = -1 + $\$token = -1 + $\$_val = $\$rcvr.lval + $\$state = $\$n + if err_flag > 0 { + err_flag-- + } + unsafe { goto $\$stack } + } + +$\$default: + // default state action + $\$n = int($\$_def[$\$state]) + if $\$n == -2 { + if $\$rcvr.char < 0 { + $\$rcvr.char, $\$token = $\$lex1(mut $\$lex, mut $\$rcvr.lval) + } + + // look through exception table + mut xi := 0 + for { + if $\$_exca[xi+0] == -1 && int($\$_exca[xi+1]) == $\$state { + break + } + xi += 2 + } + for xi += 2; ; xi += 2 { + $\$n = int($\$_exca[xi+0]) + if $\$n < 0 || $\$n == $\$token { + break + } + } + $\$n = int($\$_exca[xi+1]) + if $\$n < 0 { + unsafe { goto ret0 } + } + } + if $\$n == 0 { + // error ... attempt to resume parsing + match err_flag { + 0 { + // brand new error + $\$lex.error($\$_error_message($\$state, $\$token))! + n_errs++ + if $\$_debug >= 1 { + print($\$_statname($\$state)) + println(" saw \${$\$_tokname($\$token)}") + } + + // Note: fallthrough copies the next case: + err_flag = 3 + + // find a state where "error" is a legal shift action + for $\$p >= 0 { + $\$n = int($\$_pact[$\$_s[$\$p].yys]) + $\$_err_code + if $\$n >= 0 && $\$n < $\$_last { + $\$state = int($\$_act[$\$n]) // simulate a shift of "error" + if int($\$_chk[$\$state]) == $\$_err_code { + unsafe { goto $\$stack } + } + } + + // the current p has no shift on "error", pop stack + if $\$_debug >= 2 { + println("error recovery pops state \${$\$_s[$\$p].yys}") + } + $\$p-- + } + // there is no state on the stack with an error shift ... abort + unsafe { goto ret1 } + } + + 1, 2 { + // incompletely recovered error ... try again + err_flag = 3 + + // find a state where "error" is a legal shift action + for $\$p >= 0 { + $\$n = int($\$_pact[$\$_s[$\$p].yys]) + $\$_err_code + if $\$n >= 0 && $\$n < $\$_last { + $\$state = int($\$_act[$\$n]) // simulate a shift of "error" + if int($\$_chk[$\$state]) == $\$_err_code { + unsafe { goto $\$stack } + } + } + + // the current p has no shift on "error", pop stack + if $\$_debug >= 2 { + println("error recovery pops state \${$\$_s[$\$p].yys}") + } + $\$p-- + } + // there is no state on the stack with an error shift ... abort + unsafe { goto ret1 } + } + + 3 { + // no shift yet; clobber input char + if $\$_debug >= 2 { + println("error recovery discards \${$\$_tokname($\$token)}") + } + if $\$token == $\$_eof_code { + unsafe { goto ret1 } + } + $\$rcvr.char = -1 + $\$token = -1 + unsafe { + goto $\$newstate + // try again in the same state + } + } + + else {} + } + } + + // reduction by production $\$n + if $\$_debug >= 2 { + println("reduce \${$\$n} in:\\n\\t\${$\$_statname($\$state)}") + } + + $\$nt := $\$n + $\$pt := $\$p + _ = $\$pt // guard against "declared and not used" + + $\$p -= int($\$_r2[$\$n]) + // $\$p is now the index of \$0. Perform the default action. Iff the + // reduced production is ε, \$1 is possibly out of range. + if $\$p+1 >= $\$_s.len { + mut nyys := []YYSymType{len: $\$_s.len*2} + gocopy(mut nyys, $\$_s) + $\$_s = nyys.clone() + } + $\$_val = $\$_s[$\$p+1] + + // consult goto table to find next state + $\$n = int($\$_r1[$\$n]) + $\$g := int($\$_pgo[$\$n]) + $\$j := $\$g + $\$_s[$\$p].yys + 1 + + if $\$j >= $\$_last { + $\$state = int($\$_act[$\$g]) + } else { + $\$state = int($\$_act[$\$j]) + if int($\$_chk[$\$state]) != -$\$n { + $\$state = int($\$_act[$\$g]) + } + } + // dummy call; replaced with literal code + $\$run() + unsafe { + goto $\$stack + // stack new state and value + } +} + +fn gocopy[T](mut dst []T, src []T) int { + mut min := dst.len + if src.len < min { + min = src.len + } + for i := 0; i < min; i++ { + dst[i] = src[i] + } + return src.len +} +' + +fn slice_str[T](ss []T) string { + mut s := '[' + for i, a in ss { + if i > 0 { + s += ' ' + } + s += '${a}' + } + s += ']' + return s +} + +fn (y Vyacc) dump(msg string) { + println('--- ${msg}') + println('nstate = ${y.nstate}') + println('tystate = ${slice_str(y.tystate[..y.nstate])}') + println('wsets (${y.ntokens})') + for i, w in y.wsets[..y.ntokens] { + println(' ${i}: ${pitem_str(w.pitem)} ${w.flag} ${w.ws}') + } + println('temp1 = ${slice_str(y.temp1[..50])}') +} + +fn pitem_str(p Pitem) string { + if p.prod.len == 0 { + return '{[] ${p.off} ${p.first} ${p.prodno}}' + } + if p.prod.len == 2 { + return '{[${p.prod[0]} ${p.prod[1]}] ${p.off} ${p.first} ${p.prodno}}' + } + if p.prod.len == 4 { + return '{[${p.prod[0]} ${p.prod[1]} ${p.prod[2]} ${p.prod[3]}] ${p.off} ${p.first} ${p.prodno}}' + } + return 'BAD ${p.prod.len}' +} diff --git a/tests/catalogs.sql b/tests/catalogs.sql index de10bda..b4faf56 100644 --- a/tests/catalogs.sql +++ b/tests/catalogs.sql @@ -1,11 +1,11 @@ -CREATE TABLE ":memory:".PUBLIC.foo (baz INTEGER); -INSERT INTO ":memory:".PUBLIC.foo (baz) VALUES (123); -INSERT INTO ":memory:".PUBLIC.foo (baz) VALUES (456); -SELECT * FROM ":memory:".PUBLIC.foo; -UPDATE ":memory:".PUBLIC.foo SET baz = 789 WHERE baz = 123; -SELECT * FROM ":memory:".PUBLIC.foo; -DELETE FROM ":memory:".PUBLIC.foo WHERE baz > 700; -SELECT * FROM ":memory:".PUBLIC.foo; +CREATE TABLE PUBLIC.foo (baz INTEGER); +INSERT INTO PUBLIC.foo (baz) VALUES (123); +INSERT INTO PUBLIC.foo (baz) VALUES (456); +SELECT * FROM PUBLIC.foo; +UPDATE PUBLIC.foo SET baz = 789 WHERE baz = 123; +SELECT * FROM PUBLIC.foo; +DELETE FROM PUBLIC.foo WHERE baz > 700; +SELECT * FROM PUBLIC.foo; -- msg: CREATE TABLE 1 -- msg: INSERT 1 -- msg: INSERT 1 @@ -18,17 +18,21 @@ SELECT * FROM ":memory:".PUBLIC.foo; -- BAZ: 456 /* create_catalog FOO */ -CREATE TABLE foo.public.bar (baz INTEGER); -EXPLAIN SELECT * FROM foo.public.bar; +SET CATALOG 'FOO'; +CREATE TABLE public.bar (baz INTEGER); +EXPLAIN SELECT * FROM public.bar; +-- msg: SET CATALOG 1 -- msg: CREATE TABLE 1 -- EXPLAIN: TABLE FOO.PUBLIC.BAR (BAZ INTEGER) -- EXPLAIN: EXPR (FOO.PUBLIC.BAR.BAZ INTEGER) /* create_catalog FOO */ -CREATE TABLE foo.public.bar (baz INTEGER); -INSERT INTO foo.public.bar (baz) VALUES (123); -EXPLAIN SELECT * FROM foo.public.bar; +SET CATALOG 'FOO'; +CREATE TABLE public.bar (baz INTEGER); +INSERT INTO public.bar (baz) VALUES (123); +EXPLAIN SELECT * FROM public.bar; SET CATALOG ':memory:'; +-- msg: SET CATALOG 1 -- msg: CREATE TABLE 1 -- msg: INSERT 1 -- EXPLAIN: TABLE FOO.PUBLIC.BAR (BAZ INTEGER) @@ -51,10 +55,10 @@ SELECT * FROM baz; CREATE TABLE baz (num2 INTEGER); INSERT INTO baz (num2) VALUES (456); SELECT * FROM baz; -SET CATALOG ':memory:'; -SELECT * FROM foo.public.baz; -SELECT * FROM bar.public.baz; -SELECT * FROM foo.public.baz JOIN bar.public.baz ON TRUE; +SET CATALOG 'FOO'; +SELECT * FROM public.baz; +SET CATALOG 'BAR'; +SELECT * FROM public.baz; -- COL1: :memory: -- COL1: BAR -- msg: SET CATALOG 1 @@ -71,5 +75,5 @@ SELECT * FROM foo.public.baz JOIN bar.public.baz ON TRUE; -- NUM2: 456 -- msg: SET CATALOG 1 -- NUM2: 456 +-- msg: SET CATALOG 1 -- NUM1: 123 --- NUM2: 456 NUM1: 123 diff --git a/tests/character-varying.sql b/tests/character-varying.sql index d98f291..5b7d8a2 100644 --- a/tests/character-varying.sql +++ b/tests/character-varying.sql @@ -18,3 +18,9 @@ SELECT CAST(x AS CHAR(10)), CHARACTER_LENGTH(CAST(x AS CHAR(10))) FROM foo; -- msg: CREATE TABLE 1 -- msg: INSERT 1 -- COL1: hello COL2: 10 + +VALUES CAST('ABC' AS CHAR(3)) = CAST('ABC' AS VARCHAR(3)); +-- COL1: TRUE + +VALUES CAST('ABC' AS CHAR(3)) = CAST('DEF' AS VARCHAR(3)); +-- COL1: FALSE diff --git a/tests/delete.sql b/tests/delete.sql index 6b12440..735d817 100644 --- a/tests/delete.sql +++ b/tests/delete.sql @@ -35,3 +35,17 @@ DELETE FROM foo.bar; -- msg: CREATE SCHEMA 1 -- msg: CREATE TABLE 1 -- msg: DELETE 0 + +-- # https://github.com/elliotchance/vsql/issues/200 +CREATE TABLE PrimaryProduct (id INT NOT NULL, PRIMARY KEY(id)); +INSERT INTO PrimaryProduct (id) VALUES (1); +INSERT INTO PrimaryProduct (id) VALUES (2); +EXPLAIN DELETE FROM PrimaryProduct WHERE id = 1; +DELETE FROM PrimaryProduct WHERE id = 1; +SELECT * FROM PrimaryProduct; +-- msg: CREATE TABLE 1 +-- msg: INSERT 1 +-- msg: INSERT 1 +-- EXPLAIN: PRIMARY KEY ":memory:".PUBLIC.PRIMARYPRODUCT (ID INTEGER NOT NULL) BETWEEN 1 AND 1 +-- msg: DELETE 1 +-- ID: 2 diff --git a/tests/double-precision.sql b/tests/double-precision.sql index 25f1820..84530e9 100644 --- a/tests/double-precision.sql +++ b/tests/double-precision.sql @@ -77,11 +77,11 @@ VALUES 53.7e0 - CAST(1.23 AS DOUBLE PRECISION); /* types */ VALUES CAST(-2000000000.1 AS DOUBLE PRECISION) - 500000000.7e0; --- COL1: -0.25000000008e+10 (DOUBLE PRECISION) +-- COL1: -2.5000000008e+09 (DOUBLE PRECISION) /* types */ VALUES -500000000.7e0 - CAST(2000000000.1 AS DOUBLE PRECISION); --- COL1: -0.25000000008e+10 (DOUBLE PRECISION) +-- COL1: -2.5000000008e+09 (DOUBLE PRECISION) /* types */ VALUES CAST(12.3 AS DOUBLE PRECISION) * 53.7e0; @@ -93,11 +93,11 @@ VALUES -53.7e0 * CAST(12.3 AS DOUBLE PRECISION); /* types */ VALUES CAST(-300000.1 AS DOUBLE PRECISION) * 200000.7e0; --- COL1: -6.000023000007e+10 (DOUBLE PRECISION) +-- COL1: -6.00002300001e+10 (DOUBLE PRECISION) /* types */ VALUES -300000.7e0 * CAST(200000.1 AS DOUBLE PRECISION); --- COL1: -6.000017000007e+10 (DOUBLE PRECISION) +-- COL1: -6.00001700001e+10 (DOUBLE PRECISION) /* types */ VALUES CAST(1.23 AS DOUBLE PRECISION) / 53.7e0; @@ -128,5 +128,5 @@ SELECT * FROM foo; SELECT CAST(x AS BIGINT) FROM foo; -- msg: CREATE TABLE 1 -- msg: INSERT 1 --- X: 0.1234567891235e+27 +-- X: 1.23456789123e+26 -- error 22003: numeric value out of range diff --git a/tests/errors.sql b/tests/errors.sql index 29b6d6c..bfb699b 100644 --- a/tests/errors.sql +++ b/tests/errors.sql @@ -1,5 +1,5 @@ TABLE; --- error 42601: syntax error: near "TABLE" +-- error 42601: syntax error: unexpected TABLE SELECT 1; DELETE; --- error 42601: syntax error: near "DELETE" +-- error 42601: syntax error: unexpected ";", expecting FROM or "," diff --git a/tests/fold.sql b/tests/fold.sql index 2b6ab60..13b3879 100644 --- a/tests/fold.sql +++ b/tests/fold.sql @@ -13,7 +13,7 @@ VALUES LOWER(TRUE); -- error 42883: function does not exist: LOWER(BOOLEAN) VALUES UPPER(); --- error 42601: syntax error: near ")" +-- error 42601: syntax error: unexpected ")" VALUES LOWER('abc', 123); --- error 42601: syntax error: near "," +-- error 42601: syntax error: unexpected ",", expecting ")" or "||" diff --git a/tests/length.sql b/tests/length.sql index de9d36d..54183f2 100644 --- a/tests/length.sql +++ b/tests/length.sql @@ -18,3 +18,7 @@ VALUES OCTET_LENGTH('😊£'); VALUES char_length('😊£'); -- COL1: 2 + +/* types */ +VALUES CHAR_LENGTH(CAST('hello Hello' AS CHAR(30))); +-- COL1: 30 (INTEGER) diff --git a/tests/math.sql b/tests/math.sql index 567ce4d..5cc6f6b 100644 --- a/tests/math.sql +++ b/tests/math.sql @@ -10,10 +10,10 @@ VALUES ABS('hello'); -- error 42883: function does not exist: ABS(CHARACTER(5)) VALUES ABS(); --- error 42601: syntax error: near ")" +-- error 42601: syntax error: unexpected ")" VALUES ABS(1, 2); --- error 42601: syntax error: near "," +-- error 42601: syntax error: unexpected ",", expecting ")" or "+" or "-" /* types */ VALUES MOD(232.0, 3.0); diff --git a/tests/real.sql b/tests/real.sql index b3cbfaa..066c52d 100644 --- a/tests/real.sql +++ b/tests/real.sql @@ -93,11 +93,11 @@ VALUES CAST(-53.7 AS REAL) * CAST(12.3 AS REAL); /* types */ VALUES CAST(-300000.1 AS REAL) * CAST(200000.7 AS REAL); --- COL1: -6.000023e+10 (REAL) +-- COL1: -6.00002e+10 (REAL) /* types */ VALUES CAST(-300000.7 AS REAL) * CAST(200000.1 AS REAL); --- COL1: -0.6000017e+11 (REAL) +-- COL1: -6.00002e+10 (REAL) /* types */ VALUES CAST(1.23 AS REAL) / CAST(53.7 AS REAL); diff --git a/tests/reserved-words.sql b/tests/reserved-words.sql index 33b8bfd..187f23f 100644 --- a/tests/reserved-words.sql +++ b/tests/reserved-words.sql @@ -1,1106 +1,1106 @@ CREATE TABLE ABS (x INT); --- error 42601: syntax error: near "ABS" +-- error 42601: syntax error: unexpected ABS CREATE TABLE ACOS (x INT); --- error 42601: syntax error: near "ACOS" +-- error 42601: syntax error: unexpected ACOS CREATE TABLE ALL (x INT); --- error 42601: syntax error: near "ALL" +-- error 42601: syntax error: unexpected ALL CREATE TABLE ALLOCATE (x INT); --- error 42601: syntax error: near "ALLOCATE" +-- error 42601: syntax error: unexpected ALLOCATE CREATE TABLE ALTER (x INT); --- error 42601: syntax error: near "ALTER" +-- error 42601: syntax error: unexpected ALTER CREATE TABLE AND (x INT); --- error 42601: syntax error: near "AND" +-- error 42601: syntax error: unexpected AND CREATE TABLE ANY (x INT); --- error 42601: syntax error: near "ANY" +-- error 42601: syntax error: unexpected ANY CREATE TABLE ARE (x INT); --- error 42601: syntax error: near "ARE" +-- error 42601: syntax error: unexpected ARE CREATE TABLE ARRAY (x INT); --- error 42601: syntax error: near "ARRAY" +-- error 42601: syntax error: unexpected ARRAY CREATE TABLE ARRAY_AGG (x INT); --- error 42601: syntax error: near "ARRAY_AGG" +-- error 42601: syntax error: unexpected ARRAY_AGG CREATE TABLE ARRAY_MAX_CARDINALITY (x INT); --- error 42601: syntax error: near "ARRAY_MAX_CARDINALITY" +-- error 42601: syntax error: unexpected ARRAY_MAX_CARDINALITY CREATE TABLE AS (x INT); --- error 42601: syntax error: near "AS" +-- error 42601: syntax error: unexpected AS CREATE TABLE ASENSITIVE (x INT); --- error 42601: syntax error: near "ASENSITIVE" +-- error 42601: syntax error: unexpected ASENSITIVE CREATE TABLE ASIN (x INT); --- error 42601: syntax error: near "ASIN" +-- error 42601: syntax error: unexpected ASIN CREATE TABLE ASYMMETRIC (x INT); --- error 42601: syntax error: near "ASYMMETRIC" +-- error 42601: syntax error: unexpected ASYMMETRIC CREATE TABLE AT (x INT); --- error 42601: syntax error: near "AT" +-- error 42601: syntax error: unexpected AT CREATE TABLE ATAN (x INT); --- error 42601: syntax error: near "ATAN" +-- error 42601: syntax error: unexpected ATAN CREATE TABLE ATOMIC (x INT); --- error 42601: syntax error: near "ATOMIC" +-- error 42601: syntax error: unexpected ATOMIC CREATE TABLE AUTHORIZATION (x INT); --- error 42601: syntax error: near "AUTHORIZATION" +-- error 42601: syntax error: unexpected AUTHORIZATION CREATE TABLE AVG (x INT); --- error 42601: syntax error: near "AVG" +-- error 42601: syntax error: unexpected AVG CREATE TABLE BEGIN (x INT); --- error 42601: syntax error: near "BEGIN" +-- error 42601: syntax error: unexpected BEGIN CREATE TABLE BEGIN_FRAME (x INT); --- error 42601: syntax error: near "BEGIN_FRAME" +-- error 42601: syntax error: unexpected BEGIN_FRAME CREATE TABLE BEGIN_PARTITION (x INT); --- error 42601: syntax error: near "BEGIN_PARTITION" +-- error 42601: syntax error: unexpected BEGIN_PARTITION CREATE TABLE BETWEEN (x INT); --- error 42601: syntax error: near "BETWEEN" +-- error 42601: syntax error: unexpected BETWEEN CREATE TABLE BIGINT (x INT); --- error 42601: syntax error: near "BIGINT" +-- error 42601: syntax error: unexpected BIGINT CREATE TABLE BINARY (x INT); --- error 42601: syntax error: near "BINARY" +-- error 42601: syntax error: unexpected BINARY CREATE TABLE BLOB (x INT); --- error 42601: syntax error: near "BLOB" +-- error 42601: syntax error: unexpected BLOB CREATE TABLE BOOLEAN (x INT); --- error 42601: syntax error: near "BOOLEAN" +-- error 42601: syntax error: unexpected BOOLEAN CREATE TABLE BOTH (x INT); --- error 42601: syntax error: near "BOTH" +-- error 42601: syntax error: unexpected BOTH CREATE TABLE BY (x INT); --- error 42601: syntax error: near "BY" +-- error 42601: syntax error: unexpected BY CREATE TABLE CALL (x INT); --- error 42601: syntax error: near "CALL" +-- error 42601: syntax error: unexpected CALL CREATE TABLE CALLED (x INT); --- error 42601: syntax error: near "CALLED" +-- error 42601: syntax error: unexpected CALLED CREATE TABLE CARDINALITY (x INT); --- error 42601: syntax error: near "CARDINALITY" +-- error 42601: syntax error: unexpected CARDINALITY CREATE TABLE CASCADED (x INT); --- error 42601: syntax error: near "CASCADED" +-- error 42601: syntax error: unexpected CASCADED CREATE TABLE CASE (x INT); --- error 42601: syntax error: near "CASE" +-- error 42601: syntax error: unexpected CASE CREATE TABLE CAST (x INT); --- error 42601: syntax error: near "CAST" +-- error 42601: syntax error: unexpected CAST CREATE TABLE CEIL (x INT); --- error 42601: syntax error: near "CEIL" +-- error 42601: syntax error: unexpected CEIL CREATE TABLE CEILING (x INT); --- error 42601: syntax error: near "CEILING" +-- error 42601: syntax error: unexpected CEILING CREATE TABLE CHAR (x INT); --- error 42601: syntax error: near "CHAR" +-- error 42601: syntax error: unexpected CHAR CREATE TABLE CHAR_LENGTH (x INT); --- error 42601: syntax error: near "CHAR_LENGTH" +-- error 42601: syntax error: unexpected CHAR_LENGTH CREATE TABLE CHARACTER (x INT); --- error 42601: syntax error: near "CHARACTER" +-- error 42601: syntax error: unexpected CHARACTER CREATE TABLE CHARACTER_LENGTH (x INT); --- error 42601: syntax error: near "CHARACTER_LENGTH" +-- error 42601: syntax error: unexpected CHARACTER_LENGTH CREATE TABLE CHECK (x INT); --- error 42601: syntax error: near "CHECK" +-- error 42601: syntax error: unexpected CHECK CREATE TABLE CLASSIFIER (x INT); --- error 42601: syntax error: near "CLASSIFIER" +-- error 42601: syntax error: unexpected CLASSIFIER CREATE TABLE CLOB (x INT); --- error 42601: syntax error: near "CLOB" +-- error 42601: syntax error: unexpected CLOB CREATE TABLE CLOSE (x INT); --- error 42601: syntax error: near "CLOSE" +-- error 42601: syntax error: unexpected CLOSE CREATE TABLE COALESCE (x INT); --- error 42601: syntax error: near "COALESCE" +-- error 42601: syntax error: unexpected COALESCE CREATE TABLE COLLATE (x INT); --- error 42601: syntax error: near "COLLATE" +-- error 42601: syntax error: unexpected COLLATE CREATE TABLE COLLECT (x INT); --- error 42601: syntax error: near "COLLECT" +-- error 42601: syntax error: unexpected COLLECT CREATE TABLE COLUMN (x INT); --- error 42601: syntax error: near "COLUMN" +-- error 42601: syntax error: unexpected COLUMN CREATE TABLE COMMIT (x INT); --- error 42601: syntax error: near "COMMIT" +-- error 42601: syntax error: unexpected COMMIT CREATE TABLE CONDITION (x INT); --- error 42601: syntax error: near "CONDITION" +-- error 42601: syntax error: unexpected CONDITION CREATE TABLE CONNECT (x INT); --- error 42601: syntax error: near "CONNECT" +-- error 42601: syntax error: unexpected CONNECT CREATE TABLE CONSTRAINT (x INT); --- error 42601: syntax error: near "CONSTRAINT" +-- error 42601: syntax error: unexpected CONSTRAINT CREATE TABLE CONTAINS (x INT); --- error 42601: syntax error: near "CONTAINS" +-- error 42601: syntax error: unexpected CONTAINS CREATE TABLE CONVERT (x INT); --- error 42601: syntax error: near "CONVERT" +-- error 42601: syntax error: unexpected CONVERT CREATE TABLE COPY (x INT); --- error 42601: syntax error: near "COPY" +-- error 42601: syntax error: unexpected COPY CREATE TABLE CORR (x INT); --- error 42601: syntax error: near "CORR" +-- error 42601: syntax error: unexpected CORR CREATE TABLE CORRESPONDING (x INT); --- error 42601: syntax error: near "CORRESPONDING" +-- error 42601: syntax error: unexpected CORRESPONDING CREATE TABLE COS (x INT); --- error 42601: syntax error: near "COS" +-- error 42601: syntax error: unexpected COS CREATE TABLE COSH (x INT); --- error 42601: syntax error: near "COSH" +-- error 42601: syntax error: unexpected COSH CREATE TABLE COUNT (x INT); --- error 42601: syntax error: near "COUNT" +-- error 42601: syntax error: unexpected COUNT CREATE TABLE COVAR_POP (x INT); --- error 42601: syntax error: near "COVAR_POP" +-- error 42601: syntax error: unexpected COVAR_POP CREATE TABLE COVAR_SAMP (x INT); --- error 42601: syntax error: near "COVAR_SAMP" +-- error 42601: syntax error: unexpected COVAR_SAMP CREATE TABLE CREATE (x INT); --- error 42601: syntax error: near "CREATE" +-- error 42601: syntax error: unexpected CREATE CREATE TABLE CROSS (x INT); --- error 42601: syntax error: near "CROSS" +-- error 42601: syntax error: unexpected CROSS CREATE TABLE CUBE (x INT); --- error 42601: syntax error: near "CUBE" +-- error 42601: syntax error: unexpected CUBE CREATE TABLE CUME_DIST (x INT); --- error 42601: syntax error: near "CUME_DIST" +-- error 42601: syntax error: unexpected CUME_DIST CREATE TABLE CURRENT (x INT); --- error 42601: syntax error: near "CURRENT" +-- error 42601: syntax error: unexpected CURRENT CREATE TABLE CURRENT_CATALOG (x INT); --- error 42601: syntax error: near "CURRENT_CATALOG" +-- error 42601: syntax error: unexpected CURRENT_CATALOG CREATE TABLE CURRENT_DATE (x INT); --- error 42601: syntax error: near "CURRENT_DATE" +-- error 42601: syntax error: unexpected CURRENT_DATE CREATE TABLE CURRENT_DEFAULT_TRANSFORM_GROUP (x INT); --- error 42601: syntax error: near "CURRENT_DEFAULT_TRANSFORM_GROUP" +-- error 42601: syntax error: unexpected CURRENT_DEFAULT_TRANSFORM_GROUP CREATE TABLE CURRENT_PATH (x INT); --- error 42601: syntax error: near "CURRENT_PATH" +-- error 42601: syntax error: unexpected CURRENT_PATH CREATE TABLE CURRENT_ROLE (x INT); --- error 42601: syntax error: near "CURRENT_ROLE" +-- error 42601: syntax error: unexpected CURRENT_ROLE CREATE TABLE CURRENT_ROW (x INT); --- error 42601: syntax error: near "CURRENT_ROW" +-- error 42601: syntax error: unexpected CURRENT_ROW CREATE TABLE CURRENT_SCHEMA (x INT); --- error 42601: syntax error: near "CURRENT_SCHEMA" +-- error 42601: syntax error: unexpected CURRENT_SCHEMA CREATE TABLE CURRENT_TIME (x INT); --- error 42601: syntax error: near "CURRENT_TIME" +-- error 42601: syntax error: unexpected CURRENT_TIME CREATE TABLE CURRENT_TIMESTAMP (x INT); --- error 42601: syntax error: near "CURRENT_TIMESTAMP" +-- error 42601: syntax error: unexpected CURRENT_TIMESTAMP CREATE TABLE CURRENT_PATH (x INT); --- error 42601: syntax error: near "CURRENT_PATH" +-- error 42601: syntax error: unexpected CURRENT_PATH CREATE TABLE CURRENT_ROLE (x INT); --- error 42601: syntax error: near "CURRENT_ROLE" +-- error 42601: syntax error: unexpected CURRENT_ROLE CREATE TABLE CURRENT_TRANSFORM_GROUP_FOR_TYPE (x INT); --- error 42601: syntax error: near "CURRENT_TRANSFORM_GROUP_FOR_TYPE" +-- error 42601: syntax error: unexpected CURRENT_TRANSFORM_GROUP_FOR_TYPE CREATE TABLE CURRENT_USER (x INT); --- error 42601: syntax error: near "CURRENT_USER" +-- error 42601: syntax error: unexpected CURRENT_USER CREATE TABLE CURSOR (x INT); --- error 42601: syntax error: near "CURSOR" +-- error 42601: syntax error: unexpected CURSOR CREATE TABLE CYCLE (x INT); --- error 42601: syntax error: near "CYCLE" +-- error 42601: syntax error: unexpected CYCLE CREATE TABLE DATE (x INT); --- error 42601: syntax error: near "DATE" +-- error 42601: syntax error: unexpected DATE CREATE TABLE DAY (x INT); --- error 42601: syntax error: near "DAY" +-- error 42601: syntax error: unexpected DAY CREATE TABLE DEALLOCATE (x INT); --- error 42601: syntax error: near "DEALLOCATE" +-- error 42601: syntax error: unexpected DEALLOCATE CREATE TABLE DEC (x INT); --- error 42601: syntax error: near "DEC" +-- error 42601: syntax error: unexpected DEC CREATE TABLE DECIMAL (x INT); --- error 42601: syntax error: near "DECIMAL" +-- error 42601: syntax error: unexpected DECIMAL CREATE TABLE DECFLOAT (x INT); --- error 42601: syntax error: near "DECFLOAT" +-- error 42601: syntax error: unexpected DECFLOAT CREATE TABLE DECLARE (x INT); --- error 42601: syntax error: near "DECLARE" +-- error 42601: syntax error: unexpected DECLARE CREATE TABLE DEFAULT (x INT); --- error 42601: syntax error: near "DEFAULT" +-- error 42601: syntax error: unexpected DEFAULT CREATE TABLE DEFINE (x INT); --- error 42601: syntax error: near "DEFINE" +-- error 42601: syntax error: unexpected DEFINE CREATE TABLE DELETE (x INT); --- error 42601: syntax error: near "DELETE" +-- error 42601: syntax error: unexpected DELETE CREATE TABLE DENSE_RANK (x INT); --- error 42601: syntax error: near "DENSE_RANK" +-- error 42601: syntax error: unexpected DENSE_RANK CREATE TABLE DEREF (x INT); --- error 42601: syntax error: near "DEREF" +-- error 42601: syntax error: unexpected DEREF CREATE TABLE DESCRIBE (x INT); --- error 42601: syntax error: near "DESCRIBE" +-- error 42601: syntax error: unexpected DESCRIBE CREATE TABLE DETERMINISTIC (x INT); --- error 42601: syntax error: near "DETERMINISTIC" +-- error 42601: syntax error: unexpected DETERMINISTIC CREATE TABLE DISCONNECT (x INT); --- error 42601: syntax error: near "DISCONNECT" +-- error 42601: syntax error: unexpected DISCONNECT CREATE TABLE DISTINCT (x INT); --- error 42601: syntax error: near "DISTINCT" +-- error 42601: syntax error: unexpected DISTINCT CREATE TABLE DOUBLE (x INT); --- error 42601: syntax error: near "DOUBLE" +-- error 42601: syntax error: unexpected DOUBLE CREATE TABLE DROP (x INT); --- error 42601: syntax error: near "DROP" +-- error 42601: syntax error: unexpected DROP CREATE TABLE DYNAMIC (x INT); --- error 42601: syntax error: near "DYNAMIC" +-- error 42601: syntax error: unexpected DYNAMIC CREATE TABLE EACH (x INT); --- error 42601: syntax error: near "EACH" +-- error 42601: syntax error: unexpected EACH CREATE TABLE ELEMENT (x INT); --- error 42601: syntax error: near "ELEMENT" +-- error 42601: syntax error: unexpected ELEMENT CREATE TABLE ELSE (x INT); --- error 42601: syntax error: near "ELSE" +-- error 42601: syntax error: unexpected ELSE CREATE TABLE EMPTY (x INT); --- error 42601: syntax error: near "EMPTY" +-- error 42601: syntax error: unexpected EMPTY CREATE TABLE END (x INT); --- error 42601: syntax error: near "END" +-- error 42601: syntax error: unexpected END CREATE TABLE END_FRAME (x INT); --- error 42601: syntax error: near "END_FRAME" +-- error 42601: syntax error: unexpected END_FRAME CREATE TABLE END_PARTITION (x INT); --- error 42601: syntax error: near "END_PARTITION" +-- error 42601: syntax error: unexpected END_PARTITION CREATE TABLE END-EXEC (x INT); --- error 42601: syntax error: near "END" +-- error 42601: syntax error: unexpected END CREATE TABLE EQUALS (x INT); --- error 42601: syntax error: near "EQUALS" +-- error 42601: syntax error: unexpected EQUALS CREATE TABLE ESCAPE (x INT); --- error 42601: syntax error: near "ESCAPE" +-- error 42601: syntax error: unexpected ESCAPE CREATE TABLE EVERY (x INT); --- error 42601: syntax error: near "EVERY" +-- error 42601: syntax error: unexpected EVERY CREATE TABLE EXCEPT (x INT); --- error 42601: syntax error: near "EXCEPT" +-- error 42601: syntax error: unexpected EXCEPT CREATE TABLE EXEC (x INT); --- error 42601: syntax error: near "EXEC" +-- error 42601: syntax error: unexpected EXEC CREATE TABLE EXECUTE (x INT); --- error 42601: syntax error: near "EXECUTE" +-- error 42601: syntax error: unexpected EXECUTE CREATE TABLE EXISTS (x INT); --- error 42601: syntax error: near "EXISTS" +-- error 42601: syntax error: unexpected EXISTS CREATE TABLE EXP (x INT); --- error 42601: syntax error: near "EXP" +-- error 42601: syntax error: unexpected EXP CREATE TABLE EXTERNAL (x INT); --- error 42601: syntax error: near "EXTERNAL" +-- error 42601: syntax error: unexpected EXTERNAL CREATE TABLE EXTRACT (x INT); --- error 42601: syntax error: near "EXTRACT" +-- error 42601: syntax error: unexpected EXTRACT CREATE TABLE FALSE (x INT); --- error 42601: syntax error: near "FALSE" +-- error 42601: syntax error: unexpected FALSE CREATE TABLE FETCH (x INT); --- error 42601: syntax error: near "FETCH" +-- error 42601: syntax error: unexpected FETCH CREATE TABLE FILTER (x INT); --- error 42601: syntax error: near "FILTER" +-- error 42601: syntax error: unexpected FILTER CREATE TABLE FIRST_VALUE (x INT); --- error 42601: syntax error: near "FIRST_VALUE" +-- error 42601: syntax error: unexpected FIRST_VALUE CREATE TABLE FLOAT (x INT); --- error 42601: syntax error: near "FLOAT" +-- error 42601: syntax error: unexpected FLOAT CREATE TABLE FLOOR (x INT); --- error 42601: syntax error: near "FLOOR" +-- error 42601: syntax error: unexpected FLOOR CREATE TABLE FOR (x INT); --- error 42601: syntax error: near "FOR" +-- error 42601: syntax error: unexpected FOR CREATE TABLE FOREIGN (x INT); --- error 42601: syntax error: near "FOREIGN" +-- error 42601: syntax error: unexpected FOREIGN CREATE TABLE FRAME_ROW (x INT); --- error 42601: syntax error: near "FRAME_ROW" +-- error 42601: syntax error: unexpected FRAME_ROW CREATE TABLE FREE (x INT); --- error 42601: syntax error: near "FREE" +-- error 42601: syntax error: unexpected FREE CREATE TABLE FROM (x INT); --- error 42601: syntax error: near "FROM" +-- error 42601: syntax error: unexpected FROM CREATE TABLE FULL (x INT); --- error 42601: syntax error: near "FULL" +-- error 42601: syntax error: unexpected FULL CREATE TABLE FUNCTION (x INT); --- error 42601: syntax error: near "FUNCTION" +-- error 42601: syntax error: unexpected FUNCTION CREATE TABLE FUSION (x INT); --- error 42601: syntax error: near "FUSION" +-- error 42601: syntax error: unexpected FUSION CREATE TABLE GET (x INT); --- error 42601: syntax error: near "GET" +-- error 42601: syntax error: unexpected GET CREATE TABLE GLOBAL (x INT); --- error 42601: syntax error: near "GLOBAL" +-- error 42601: syntax error: unexpected GLOBAL CREATE TABLE GRANT (x INT); --- error 42601: syntax error: near "GRANT" +-- error 42601: syntax error: unexpected GRANT CREATE TABLE GROUP (x INT); --- error 42601: syntax error: near "GROUP" +-- error 42601: syntax error: unexpected GROUP CREATE TABLE GROUPING (x INT); --- error 42601: syntax error: near "GROUPING" +-- error 42601: syntax error: unexpected GROUPING CREATE TABLE GROUPS (x INT); --- error 42601: syntax error: near "GROUPS" +-- error 42601: syntax error: unexpected GROUPS CREATE TABLE HAVING (x INT); --- error 42601: syntax error: near "HAVING" +-- error 42601: syntax error: unexpected HAVING CREATE TABLE HOLD (x INT); --- error 42601: syntax error: near "HOLD" +-- error 42601: syntax error: unexpected HOLD CREATE TABLE HOUR (x INT); --- error 42601: syntax error: near "HOUR" +-- error 42601: syntax error: unexpected HOUR CREATE TABLE IDENTITY (x INT); --- error 42601: syntax error: near "IDENTITY" +-- error 42601: syntax error: unexpected IDENTITY CREATE TABLE IN (x INT); --- error 42601: syntax error: near "IN" +-- error 42601: syntax error: unexpected IN CREATE TABLE INDICATOR (x INT); --- error 42601: syntax error: near "INDICATOR" +-- error 42601: syntax error: unexpected INDICATOR CREATE TABLE INITIAL (x INT); --- error 42601: syntax error: near "INITIAL" +-- error 42601: syntax error: unexpected INITIAL CREATE TABLE INNER (x INT); --- error 42601: syntax error: near "INNER" +-- error 42601: syntax error: unexpected INNER CREATE TABLE INOUT (x INT); --- error 42601: syntax error: near "INOUT" +-- error 42601: syntax error: unexpected INOUT CREATE TABLE INSENSITIVE (x INT); --- error 42601: syntax error: near "INSENSITIVE" +-- error 42601: syntax error: unexpected INSENSITIVE CREATE TABLE INSERT (x INT); --- error 42601: syntax error: near "INSERT" +-- error 42601: syntax error: unexpected INSERT CREATE TABLE INT (x INT); --- error 42601: syntax error: near "INT" +-- error 42601: syntax error: unexpected INT CREATE TABLE INTEGER (x INT); --- error 42601: syntax error: near "INTEGER" +-- error 42601: syntax error: unexpected INTEGER CREATE TABLE INTERSECT (x INT); --- error 42601: syntax error: near "INTERSECT" +-- error 42601: syntax error: unexpected INTERSECT CREATE TABLE INTERSECTION (x INT); --- error 42601: syntax error: near "INTERSECTION" +-- error 42601: syntax error: unexpected INTERSECTION CREATE TABLE INTERVAL (x INT); --- error 42601: syntax error: near "INTERVAL" +-- error 42601: syntax error: unexpected INTERVAL CREATE TABLE INTO (x INT); --- error 42601: syntax error: near "INTO" +-- error 42601: syntax error: unexpected INTO CREATE TABLE IS (x INT); --- error 42601: syntax error: near "IS" +-- error 42601: syntax error: unexpected IS CREATE TABLE JOIN (x INT); --- error 42601: syntax error: near "JOIN" +-- error 42601: syntax error: unexpected JOIN CREATE TABLE JSON_ARRAY (x INT); --- error 42601: syntax error: near "JSON_ARRAY" +-- error 42601: syntax error: unexpected JSON_ARRAY CREATE TABLE JSON_ARRAYAGG (x INT); --- error 42601: syntax error: near "JSON_ARRAYAGG" +-- error 42601: syntax error: unexpected JSON_ARRAYAGG CREATE TABLE JSON_EXISTS (x INT); --- error 42601: syntax error: near "JSON_EXISTS" +-- error 42601: syntax error: unexpected JSON_EXISTS CREATE TABLE JSON_OBJECT (x INT); --- error 42601: syntax error: near "JSON_OBJECT" +-- error 42601: syntax error: unexpected JSON_OBJECT CREATE TABLE JSON_OBJECTAGG (x INT); --- error 42601: syntax error: near "JSON_OBJECTAGG" +-- error 42601: syntax error: unexpected JSON_OBJECTAGG CREATE TABLE JSON_QUERY (x INT); --- error 42601: syntax error: near "JSON_QUERY" +-- error 42601: syntax error: unexpected JSON_QUERY CREATE TABLE JSON_TABLE (x INT); --- error 42601: syntax error: near "JSON_TABLE" +-- error 42601: syntax error: unexpected JSON_TABLE CREATE TABLE JSON_TABLE_PRIMITIVE (x INT); --- error 42601: syntax error: near "JSON_TABLE_PRIMITIVE" +-- error 42601: syntax error: unexpected JSON_TABLE_PRIMITIVE CREATE TABLE JSON_VALUE (x INT); --- error 42601: syntax error: near "JSON_VALUE" +-- error 42601: syntax error: unexpected JSON_VALUE CREATE TABLE LAG (x INT); --- error 42601: syntax error: near "LAG" +-- error 42601: syntax error: unexpected LAG CREATE TABLE LANGUAGE (x INT); --- error 42601: syntax error: near "LANGUAGE" +-- error 42601: syntax error: unexpected LANGUAGE CREATE TABLE LARGE (x INT); --- error 42601: syntax error: near "LARGE" +-- error 42601: syntax error: unexpected LARGE CREATE TABLE LAST_VALUE (x INT); --- error 42601: syntax error: near "LAST_VALUE" +-- error 42601: syntax error: unexpected LAST_VALUE CREATE TABLE LATERAL (x INT); --- error 42601: syntax error: near "LATERAL" +-- error 42601: syntax error: unexpected LATERAL CREATE TABLE LEAD (x INT); --- error 42601: syntax error: near "LEAD" +-- error 42601: syntax error: unexpected LEAD CREATE TABLE LEADING (x INT); --- error 42601: syntax error: near "LEADING" +-- error 42601: syntax error: unexpected LEADING CREATE TABLE LEFT (x INT); --- error 42601: syntax error: near "LEFT" +-- error 42601: syntax error: unexpected LEFT CREATE TABLE LIKE (x INT); --- error 42601: syntax error: near "LIKE" +-- error 42601: syntax error: unexpected LIKE CREATE TABLE LIKE_REGEX (x INT); --- error 42601: syntax error: near "LIKE_REGEX" +-- error 42601: syntax error: unexpected LIKE_REGEX CREATE TABLE LISTAGG (x INT); --- error 42601: syntax error: near "LISTAGG" +-- error 42601: syntax error: unexpected LISTAGG CREATE TABLE LN (x INT); --- error 42601: syntax error: near "LN" +-- error 42601: syntax error: unexpected LN CREATE TABLE LOCAL (x INT); --- error 42601: syntax error: near "LOCAL" +-- error 42601: syntax error: unexpected LOCAL CREATE TABLE LOCALTIME (x INT); --- error 42601: syntax error: near "LOCALTIME" +-- error 42601: syntax error: unexpected LOCALTIME CREATE TABLE LOCALTIMESTAMP (x INT); --- error 42601: syntax error: near "LOCALTIMESTAMP" +-- error 42601: syntax error: unexpected LOCALTIMESTAMP CREATE TABLE LOG (x INT); --- error 42601: syntax error: near "LOG" +-- error 42601: syntax error: unexpected LOG CREATE TABLE LOG10 (x INT); --- error 42601: syntax error: near "LOG10" +-- error 42601: syntax error: unexpected LOG10 CREATE TABLE LOWER (x INT); --- error 42601: syntax error: near "LOWER" +-- error 42601: syntax error: unexpected LOWER CREATE TABLE MATCH (x INT); --- error 42601: syntax error: near "MATCH" +-- error 42601: syntax error: unexpected MATCH CREATE TABLE MATCH_NUMBER (x INT); --- error 42601: syntax error: near "MATCH_NUMBER" +-- error 42601: syntax error: unexpected MATCH_NUMBER CREATE TABLE MATCH_RECOGNIZE (x INT); --- error 42601: syntax error: near "MATCH_RECOGNIZE" +-- error 42601: syntax error: unexpected MATCH_RECOGNIZE CREATE TABLE MATCHES (x INT); --- error 42601: syntax error: near "MATCHES" +-- error 42601: syntax error: unexpected MATCHES CREATE TABLE MAX (x INT); --- error 42601: syntax error: near "MAX" +-- error 42601: syntax error: unexpected MAX CREATE TABLE MEMBER (x INT); --- error 42601: syntax error: near "MEMBER" +-- error 42601: syntax error: unexpected MEMBER CREATE TABLE MERGE (x INT); --- error 42601: syntax error: near "MERGE" +-- error 42601: syntax error: unexpected MERGE CREATE TABLE METHOD (x INT); --- error 42601: syntax error: near "METHOD" +-- error 42601: syntax error: unexpected METHOD CREATE TABLE MIN (x INT); --- error 42601: syntax error: near "MIN" +-- error 42601: syntax error: unexpected MIN CREATE TABLE MINUTE (x INT); --- error 42601: syntax error: near "MINUTE" +-- error 42601: syntax error: unexpected MINUTE CREATE TABLE MOD (x INT); --- error 42601: syntax error: near "MOD" +-- error 42601: syntax error: unexpected MOD CREATE TABLE MODIFIES (x INT); --- error 42601: syntax error: near "MODIFIES" +-- error 42601: syntax error: unexpected MODIFIES CREATE TABLE MODULE (x INT); --- error 42601: syntax error: near "MODULE" +-- error 42601: syntax error: unexpected MODULE CREATE TABLE MONTH (x INT); --- error 42601: syntax error: near "MONTH" +-- error 42601: syntax error: unexpected MONTH CREATE TABLE MULTISET (x INT); --- error 42601: syntax error: near "MULTISET" +-- error 42601: syntax error: unexpected MULTISET CREATE TABLE NATIONAL (x INT); --- error 42601: syntax error: near "NATIONAL" +-- error 42601: syntax error: unexpected NATIONAL CREATE TABLE NATURAL (x INT); --- error 42601: syntax error: near "NATURAL" +-- error 42601: syntax error: unexpected NATURAL CREATE TABLE NCHAR (x INT); --- error 42601: syntax error: near "NCHAR" +-- error 42601: syntax error: unexpected NCHAR CREATE TABLE NCLOB (x INT); --- error 42601: syntax error: near "NCLOB" +-- error 42601: syntax error: unexpected NCLOB CREATE TABLE NEW (x INT); --- error 42601: syntax error: near "NEW" +-- error 42601: syntax error: unexpected NEW CREATE TABLE NO (x INT); --- error 42601: syntax error: near "NO" +-- error 42601: syntax error: unexpected NO CREATE TABLE NONE (x INT); --- error 42601: syntax error: near "NONE" +-- error 42601: syntax error: unexpected NONE CREATE TABLE NORMALIZE (x INT); --- error 42601: syntax error: near "NORMALIZE" +-- error 42601: syntax error: unexpected NORMALIZE CREATE TABLE NOT (x INT); --- error 42601: syntax error: near "NOT" +-- error 42601: syntax error: unexpected NOT CREATE TABLE NTH_VALUE (x INT); --- error 42601: syntax error: near "NTH_VALUE" +-- error 42601: syntax error: unexpected NTH_VALUE CREATE TABLE NTILE (x INT); --- error 42601: syntax error: near "NTILE" +-- error 42601: syntax error: unexpected NTILE CREATE TABLE NULL (x INT); --- error 42601: syntax error: near "NULL" +-- error 42601: syntax error: unexpected NULL CREATE TABLE NULLIF (x INT); --- error 42601: syntax error: near "NULLIF" +-- error 42601: syntax error: unexpected NULLIF CREATE TABLE NUMERIC (x INT); --- error 42601: syntax error: near "NUMERIC" +-- error 42601: syntax error: unexpected NUMERIC CREATE TABLE OCTET_LENGTH (x INT); --- error 42601: syntax error: near "OCTET_LENGTH" +-- error 42601: syntax error: unexpected OCTET_LENGTH CREATE TABLE OCCURRENCES_REGEX (x INT); --- error 42601: syntax error: near "OCCURRENCES_REGEX" +-- error 42601: syntax error: unexpected OCCURRENCES_REGEX CREATE TABLE OF (x INT); --- error 42601: syntax error: near "OF" +-- error 42601: syntax error: unexpected OF CREATE TABLE OFFSET (x INT); --- error 42601: syntax error: near "OFFSET" +-- error 42601: syntax error: unexpected OFFSET CREATE TABLE OLD (x INT); --- error 42601: syntax error: near "OLD" +-- error 42601: syntax error: unexpected OLD CREATE TABLE OMIT (x INT); --- error 42601: syntax error: near "OMIT" +-- error 42601: syntax error: unexpected OMIT CREATE TABLE ON (x INT); --- error 42601: syntax error: near "ON" +-- error 42601: syntax error: unexpected ON CREATE TABLE ONE (x INT); --- error 42601: syntax error: near "ONE" +-- error 42601: syntax error: unexpected ONE CREATE TABLE ONLY (x INT); --- error 42601: syntax error: near "ONLY" +-- error 42601: syntax error: unexpected ONLY CREATE TABLE OPEN (x INT); --- error 42601: syntax error: near "OPEN" +-- error 42601: syntax error: unexpected OPEN CREATE TABLE OR (x INT); --- error 42601: syntax error: near "OR" +-- error 42601: syntax error: unexpected OR CREATE TABLE ORDER (x INT); --- error 42601: syntax error: near "ORDER" +-- error 42601: syntax error: unexpected ORDER CREATE TABLE OUT (x INT); --- error 42601: syntax error: near "OUT" +-- error 42601: syntax error: unexpected OUT CREATE TABLE OUTER (x INT); --- error 42601: syntax error: near "OUTER" +-- error 42601: syntax error: unexpected OUTER CREATE TABLE OVER (x INT); --- error 42601: syntax error: near "OVER" +-- error 42601: syntax error: unexpected OVER CREATE TABLE OVERLAPS (x INT); --- error 42601: syntax error: near "OVERLAPS" +-- error 42601: syntax error: unexpected OVERLAPS CREATE TABLE OVERLAY (x INT); --- error 42601: syntax error: near "OVERLAY" +-- error 42601: syntax error: unexpected OVERLAY CREATE TABLE PARAMETER (x INT); --- error 42601: syntax error: near "PARAMETER" +-- error 42601: syntax error: unexpected PARAMETER CREATE TABLE PARTITION (x INT); --- error 42601: syntax error: near "PARTITION" +-- error 42601: syntax error: unexpected PARTITION CREATE TABLE PATTERN (x INT); --- error 42601: syntax error: near "PATTERN" +-- error 42601: syntax error: unexpected PATTERN CREATE TABLE PER (x INT); --- error 42601: syntax error: near "PER" +-- error 42601: syntax error: unexpected PER CREATE TABLE PERCENT (x INT); --- error 42601: syntax error: near "PERCENT" +-- error 42601: syntax error: unexpected PERCENT CREATE TABLE PERCENT_RANK (x INT); --- error 42601: syntax error: near "PERCENT_RANK" +-- error 42601: syntax error: unexpected PERCENT_RANK CREATE TABLE PERCENTILE_CONT (x INT); --- error 42601: syntax error: near "PERCENTILE_CONT" +-- error 42601: syntax error: unexpected PERCENTILE_CONT CREATE TABLE PERCENTILE_DISC (x INT); --- error 42601: syntax error: near "PERCENTILE_DISC" +-- error 42601: syntax error: unexpected PERCENTILE_DISC CREATE TABLE PERIOD (x INT); --- error 42601: syntax error: near "PERIOD" +-- error 42601: syntax error: unexpected PERIOD CREATE TABLE PORTION (x INT); --- error 42601: syntax error: near "PORTION" +-- error 42601: syntax error: unexpected PORTION CREATE TABLE POSITION (x INT); --- error 42601: syntax error: near "POSITION" +-- error 42601: syntax error: unexpected POSITION CREATE TABLE POSITION_REGEX (x INT); --- error 42601: syntax error: near "POSITION_REGEX" +-- error 42601: syntax error: unexpected POSITION_REGEX CREATE TABLE POWER (x INT); --- error 42601: syntax error: near "POWER" +-- error 42601: syntax error: unexpected POWER CREATE TABLE PRECEDES (x INT); --- error 42601: syntax error: near "PRECEDES" +-- error 42601: syntax error: unexpected PRECEDES CREATE TABLE PRECISION (x INT); --- error 42601: syntax error: near "PRECISION" +-- error 42601: syntax error: unexpected PRECISION CREATE TABLE PREPARE (x INT); --- error 42601: syntax error: near "PREPARE" +-- error 42601: syntax error: unexpected PREPARE CREATE TABLE PRIMARY (x INT); --- error 42601: syntax error: near "PRIMARY" +-- error 42601: syntax error: unexpected PRIMARY CREATE TABLE PROCEDURE (x INT); --- error 42601: syntax error: near "PROCEDURE" +-- error 42601: syntax error: unexpected PROCEDURE CREATE TABLE PTF (x INT); --- error 42601: syntax error: near "PTF" +-- error 42601: syntax error: unexpected PTF CREATE TABLE RANGE (x INT); --- error 42601: syntax error: near "RANGE" +-- error 42601: syntax error: unexpected RANGE CREATE TABLE RANK (x INT); --- error 42601: syntax error: near "RANK" +-- error 42601: syntax error: unexpected RANK CREATE TABLE READS (x INT); --- error 42601: syntax error: near "READS" +-- error 42601: syntax error: unexpected READS CREATE TABLE REAL (x INT); --- error 42601: syntax error: near "REAL" +-- error 42601: syntax error: unexpected REAL CREATE TABLE RECURSIVE (x INT); --- error 42601: syntax error: near "RECURSIVE" +-- error 42601: syntax error: unexpected RECURSIVE CREATE TABLE REF (x INT); --- error 42601: syntax error: near "REF" +-- error 42601: syntax error: unexpected REF CREATE TABLE REFERENCES (x INT); --- error 42601: syntax error: near "REFERENCES" +-- error 42601: syntax error: unexpected REFERENCES CREATE TABLE REFERENCING (x INT); --- error 42601: syntax error: near "REFERENCING" +-- error 42601: syntax error: unexpected REFERENCING CREATE TABLE REGR_AVGX (x INT); --- error 42601: syntax error: near "REGR_AVGX" +-- error 42601: syntax error: unexpected REGR_AVGX CREATE TABLE REGR_AVGY (x INT); --- error 42601: syntax error: near "REGR_AVGY" +-- error 42601: syntax error: unexpected REGR_AVGY CREATE TABLE REGR_COUNT (x INT); --- error 42601: syntax error: near "REGR_COUNT" +-- error 42601: syntax error: unexpected REGR_COUNT CREATE TABLE REGR_INTERCEPT (x INT); --- error 42601: syntax error: near "REGR_INTERCEPT" +-- error 42601: syntax error: unexpected REGR_INTERCEPT CREATE TABLE REGR_R2 (x INT); --- error 42601: syntax error: near "REGR_R2" +-- error 42601: syntax error: unexpected REGR_R2 CREATE TABLE REGR_SLOPE (x INT); --- error 42601: syntax error: near "REGR_SLOPE" +-- error 42601: syntax error: unexpected REGR_SLOPE CREATE TABLE REGR_SXX (x INT); --- error 42601: syntax error: near "REGR_SXX" +-- error 42601: syntax error: unexpected REGR_SXX CREATE TABLE REGR_SXY (x INT); --- error 42601: syntax error: near "REGR_SXY" +-- error 42601: syntax error: unexpected REGR_SXY CREATE TABLE REGR_SYY (x INT); --- error 42601: syntax error: near "REGR_SYY" +-- error 42601: syntax error: unexpected REGR_SYY CREATE TABLE RELEASE (x INT); --- error 42601: syntax error: near "RELEASE" +-- error 42601: syntax error: unexpected RELEASE CREATE TABLE RESULT (x INT); --- error 42601: syntax error: near "RESULT" +-- error 42601: syntax error: unexpected RESULT CREATE TABLE RETURN (x INT); --- error 42601: syntax error: near "RETURN" +-- error 42601: syntax error: unexpected RETURN CREATE TABLE RETURNS (x INT); --- error 42601: syntax error: near "RETURNS" +-- error 42601: syntax error: unexpected RETURNS CREATE TABLE REVOKE (x INT); --- error 42601: syntax error: near "REVOKE" +-- error 42601: syntax error: unexpected REVOKE CREATE TABLE RIGHT (x INT); --- error 42601: syntax error: near "RIGHT" +-- error 42601: syntax error: unexpected RIGHT CREATE TABLE ROLLBACK (x INT); --- error 42601: syntax error: near "ROLLBACK" +-- error 42601: syntax error: unexpected ROLLBACK CREATE TABLE ROLLUP (x INT); --- error 42601: syntax error: near "ROLLUP" +-- error 42601: syntax error: unexpected ROLLUP CREATE TABLE ROW (x INT); --- error 42601: syntax error: near "ROW" +-- error 42601: syntax error: unexpected ROW CREATE TABLE ROW_NUMBER (x INT); --- error 42601: syntax error: near "ROW_NUMBER" +-- error 42601: syntax error: unexpected ROW_NUMBER CREATE TABLE ROWS (x INT); --- error 42601: syntax error: near "ROWS" +-- error 42601: syntax error: unexpected ROWS CREATE TABLE RUNNING (x INT); --- error 42601: syntax error: near "RUNNING" +-- error 42601: syntax error: unexpected RUNNING CREATE TABLE SAVEPOINT (x INT); --- error 42601: syntax error: near "SAVEPOINT" +-- error 42601: syntax error: unexpected SAVEPOINT CREATE TABLE SCOPE (x INT); --- error 42601: syntax error: near "SCOPE" +-- error 42601: syntax error: unexpected SCOPE CREATE TABLE SCROLL (x INT); --- error 42601: syntax error: near "SCROLL" +-- error 42601: syntax error: unexpected SCROLL CREATE TABLE SEARCH (x INT); --- error 42601: syntax error: near "SEARCH" +-- error 42601: syntax error: unexpected SEARCH CREATE TABLE SECOND (x INT); --- error 42601: syntax error: near "SECOND" +-- error 42601: syntax error: unexpected SECOND CREATE TABLE SEEK (x INT); --- error 42601: syntax error: near "SEEK" +-- error 42601: syntax error: unexpected SEEK CREATE TABLE SELECT (x INT); --- error 42601: syntax error: near "SELECT" +-- error 42601: syntax error: unexpected SELECT CREATE TABLE SENSITIVE (x INT); --- error 42601: syntax error: near "SENSITIVE" +-- error 42601: syntax error: unexpected SENSITIVE CREATE TABLE SESSION_USER (x INT); --- error 42601: syntax error: near "SESSION_USER" +-- error 42601: syntax error: unexpected SESSION_USER CREATE TABLE SET (x INT); --- error 42601: syntax error: near "SET" +-- error 42601: syntax error: unexpected SET CREATE TABLE SHOW (x INT); --- error 42601: syntax error: near "SHOW" +-- error 42601: syntax error: unexpected SHOW CREATE TABLE SIMILAR (x INT); --- error 42601: syntax error: near "SIMILAR" +-- error 42601: syntax error: unexpected SIMILAR CREATE TABLE SIN (x INT); --- error 42601: syntax error: near "SIN" +-- error 42601: syntax error: unexpected SIN CREATE TABLE SINH (x INT); --- error 42601: syntax error: near "SINH" +-- error 42601: syntax error: unexpected SINH CREATE TABLE SKIP (x INT); --- error 42601: syntax error: near "SKIP" +-- error 42601: syntax error: unexpected SKIP CREATE TABLE SMALLINT (x INT); --- error 42601: syntax error: near "SMALLINT" +-- error 42601: syntax error: unexpected SMALLINT CREATE TABLE SOME (x INT); --- error 42601: syntax error: near "SOME" +-- error 42601: syntax error: unexpected SOME CREATE TABLE SPECIFIC (x INT); --- error 42601: syntax error: near "SPECIFIC" +-- error 42601: syntax error: unexpected SPECIFIC CREATE TABLE SPECIFICTYPE (x INT); --- error 42601: syntax error: near "SPECIFICTYPE" +-- error 42601: syntax error: unexpected SPECIFICTYPE CREATE TABLE SQL (x INT); --- error 42601: syntax error: near "SQL" +-- error 42601: syntax error: unexpected SQL CREATE TABLE SQLEXCEPTION (x INT); --- error 42601: syntax error: near "SQLEXCEPTION" +-- error 42601: syntax error: unexpected SQLEXCEPTION CREATE TABLE SQLSTATE (x INT); --- error 42601: syntax error: near "SQLSTATE" +-- error 42601: syntax error: unexpected SQLSTATE CREATE TABLE SQLWARNING (x INT); --- error 42601: syntax error: near "SQLWARNING" +-- error 42601: syntax error: unexpected SQLWARNING CREATE TABLE SQRT (x INT); --- error 42601: syntax error: near "SQRT" +-- error 42601: syntax error: unexpected SQRT CREATE TABLE START (x INT); --- error 42601: syntax error: near "START" +-- error 42601: syntax error: unexpected START CREATE TABLE STATIC (x INT); --- error 42601: syntax error: near "STATIC" +-- error 42601: syntax error: unexpected STATIC CREATE TABLE STDDEV_POP (x INT); --- error 42601: syntax error: near "STDDEV_POP" +-- error 42601: syntax error: unexpected STDDEV_POP CREATE TABLE STDDEV_SAMP (x INT); --- error 42601: syntax error: near "STDDEV_SAMP" +-- error 42601: syntax error: unexpected STDDEV_SAMP CREATE TABLE SUBMULTISET (x INT); --- error 42601: syntax error: near "SUBMULTISET" +-- error 42601: syntax error: unexpected SUBMULTISET CREATE TABLE SUBSET (x INT); --- error 42601: syntax error: near "SUBSET" +-- error 42601: syntax error: unexpected SUBSET CREATE TABLE SUBSTRING (x INT); --- error 42601: syntax error: near "SUBSTRING" +-- error 42601: syntax error: unexpected SUBSTRING CREATE TABLE SUBSTRING_REGEX (x INT); --- error 42601: syntax error: near "SUBSTRING_REGEX" +-- error 42601: syntax error: unexpected SUBSTRING_REGEX CREATE TABLE SUCCEEDS (x INT); --- error 42601: syntax error: near "SUCCEEDS" +-- error 42601: syntax error: unexpected SUCCEEDS CREATE TABLE SUM (x INT); --- error 42601: syntax error: near "SUM" +-- error 42601: syntax error: unexpected SUM CREATE TABLE SYMMETRIC (x INT); --- error 42601: syntax error: near "SYMMETRIC" +-- error 42601: syntax error: unexpected SYMMETRIC CREATE TABLE SYSTEM (x INT); --- error 42601: syntax error: near "SYSTEM" +-- error 42601: syntax error: unexpected SYSTEM CREATE TABLE SYSTEM_TIME (x INT); --- error 42601: syntax error: near "SYSTEM_TIME" +-- error 42601: syntax error: unexpected SYSTEM_TIME CREATE TABLE SYSTEM_USER (x INT); --- error 42601: syntax error: near "SYSTEM_USER" +-- error 42601: syntax error: unexpected SYSTEM_USER CREATE TABLE TABLE (x INT); --- error 42601: syntax error: near "TABLE" +-- error 42601: syntax error: unexpected TABLE CREATE TABLE TABLESAMPLE (x INT); --- error 42601: syntax error: near "TABLESAMPLE" +-- error 42601: syntax error: unexpected TABLESAMPLE CREATE TABLE TAN (x INT); --- error 42601: syntax error: near "TAN" +-- error 42601: syntax error: unexpected TAN CREATE TABLE TANH (x INT); --- error 42601: syntax error: near "TANH" +-- error 42601: syntax error: unexpected TANH CREATE TABLE THEN (x INT); --- error 42601: syntax error: near "THEN" +-- error 42601: syntax error: unexpected THEN CREATE TABLE TIME (x INT); --- error 42601: syntax error: near "TIME" +-- error 42601: syntax error: unexpected TIME CREATE TABLE TIMESTAMP (x INT); --- error 42601: syntax error: near "TIMESTAMP" +-- error 42601: syntax error: unexpected TIMESTAMP CREATE TABLE TIMEZONE_HOUR (x INT); --- error 42601: syntax error: near "TIMEZONE_HOUR" +-- error 42601: syntax error: unexpected TIMEZONE_HOUR CREATE TABLE TIMEZONE_MINUTE (x INT); --- error 42601: syntax error: near "TIMEZONE_MINUTE" +-- error 42601: syntax error: unexpected TIMEZONE_MINUTE CREATE TABLE TO (x INT); --- error 42601: syntax error: near "TO" +-- error 42601: syntax error: unexpected TO CREATE TABLE TRAILING (x INT); --- error 42601: syntax error: near "TRAILING" +-- error 42601: syntax error: unexpected TRAILING CREATE TABLE TRANSLATE (x INT); --- error 42601: syntax error: near "TRANSLATE" +-- error 42601: syntax error: unexpected TRANSLATE CREATE TABLE TRANSLATE_REGEX (x INT); --- error 42601: syntax error: near "TRANSLATE_REGEX" +-- error 42601: syntax error: unexpected TRANSLATE_REGEX CREATE TABLE TRANSLATION (x INT); --- error 42601: syntax error: near "TRANSLATION" +-- error 42601: syntax error: unexpected TRANSLATION CREATE TABLE TREAT (x INT); --- error 42601: syntax error: near "TREAT" +-- error 42601: syntax error: unexpected TREAT CREATE TABLE TRIGGER (x INT); --- error 42601: syntax error: near "TRIGGER" +-- error 42601: syntax error: unexpected TRIGGER CREATE TABLE TRIM (x INT); --- error 42601: syntax error: near "TRIM" +-- error 42601: syntax error: unexpected TRIM CREATE TABLE TRIM_ARRAY (x INT); --- error 42601: syntax error: near "TRIM_ARRAY" +-- error 42601: syntax error: unexpected TRIM_ARRAY CREATE TABLE TRUE (x INT); --- error 42601: syntax error: near "TRUE" +-- error 42601: syntax error: unexpected TRUE CREATE TABLE TRUNCATE (x INT); --- error 42601: syntax error: near "TRUNCATE" +-- error 42601: syntax error: unexpected TRUNCATE CREATE TABLE UESCAPE (x INT); --- error 42601: syntax error: near "UESCAPE" +-- error 42601: syntax error: unexpected UESCAPE CREATE TABLE UNION (x INT); --- error 42601: syntax error: near "UNION" +-- error 42601: syntax error: unexpected UNION CREATE TABLE UNIQUE (x INT); --- error 42601: syntax error: near "UNIQUE" +-- error 42601: syntax error: unexpected UNIQUE CREATE TABLE UNKNOWN (x INT); --- error 42601: syntax error: near "UNKNOWN" +-- error 42601: syntax error: unexpected UNKNOWN CREATE TABLE UNNEST (x INT); --- error 42601: syntax error: near "UNNEST" +-- error 42601: syntax error: unexpected UNNEST CREATE TABLE UPDATE (x INT); --- error 42601: syntax error: near "UPDATE" +-- error 42601: syntax error: unexpected UPDATE CREATE TABLE UPPER (x INT); --- error 42601: syntax error: near "UPPER" +-- error 42601: syntax error: unexpected UPPER CREATE TABLE USER (x INT); --- error 42601: syntax error: near "USER" +-- error 42601: syntax error: unexpected USER CREATE TABLE USING (x INT); --- error 42601: syntax error: near "USING" +-- error 42601: syntax error: unexpected USING CREATE TABLE VALUE (x INT); --- error 42601: syntax error: near "VALUE" +-- error 42601: syntax error: unexpected VALUE CREATE TABLE VALUES (x INT); --- error 42601: syntax error: near "VALUES" +-- error 42601: syntax error: unexpected VALUES CREATE TABLE VALUE_OF (x INT); --- error 42601: syntax error: near "VALUE_OF" +-- error 42601: syntax error: unexpected VALUE_OF CREATE TABLE VAR_POP (x INT); --- error 42601: syntax error: near "VAR_POP" +-- error 42601: syntax error: unexpected VAR_POP CREATE TABLE VAR_SAMP (x INT); --- error 42601: syntax error: near "VAR_SAMP" +-- error 42601: syntax error: unexpected VAR_SAMP CREATE TABLE VARBINARY (x INT); --- error 42601: syntax error: near "VARBINARY" +-- error 42601: syntax error: unexpected VARBINARY CREATE TABLE VARCHAR (x INT); --- error 42601: syntax error: near "VARCHAR" +-- error 42601: syntax error: unexpected VARCHAR CREATE TABLE VARYING (x INT); --- error 42601: syntax error: near "VARYING" +-- error 42601: syntax error: unexpected VARYING CREATE TABLE VERSIONING (x INT); --- error 42601: syntax error: near "VERSIONING" +-- error 42601: syntax error: unexpected VERSIONING CREATE TABLE WHEN (x INT); --- error 42601: syntax error: near "WHEN" +-- error 42601: syntax error: unexpected WHEN CREATE TABLE WHENEVER (x INT); --- error 42601: syntax error: near "WHENEVER" +-- error 42601: syntax error: unexpected WHENEVER CREATE TABLE WHERE (x INT); --- error 42601: syntax error: near "WHERE" +-- error 42601: syntax error: unexpected WHERE CREATE TABLE WIDTH_BUCKET (x INT); --- error 42601: syntax error: near "WIDTH_BUCKET" +-- error 42601: syntax error: unexpected WIDTH_BUCKET CREATE TABLE WINDOW (x INT); --- error 42601: syntax error: near "WINDOW" +-- error 42601: syntax error: unexpected WINDOW CREATE TABLE WITH (x INT); --- error 42601: syntax error: near "WITH" +-- error 42601: syntax error: unexpected WITH CREATE TABLE WITHIN (x INT); --- error 42601: syntax error: near "WITHIN" +-- error 42601: syntax error: unexpected WITHIN CREATE TABLE WITHOUT (x INT); --- error 42601: syntax error: near "WITHOUT" +-- error 42601: syntax error: unexpected WITHOUT CREATE TABLE YEAR (x INT); --- error 42601: syntax error: near "YEAR" +-- error 42601: syntax error: unexpected YEAR CREATE TABLE abs (x INT); --- error 42601: syntax error: near "ABS" +-- error 42601: syntax error: unexpected ABS CREATE TABLE foo (ABS INT); --- error 42601: syntax error: near "ABS" +-- error 42601: syntax error: unexpected ABS CREATE TABLE foo (abs int); --- error 42601: syntax error: near "ABS" +-- error 42601: syntax error: unexpected ABS diff --git a/tests/std_8_2_comparison_predicate.sql b/tests/std_8_2_comparison_predicate.sql new file mode 100644 index 0000000..28e02d0 --- /dev/null +++ b/tests/std_8_2_comparison_predicate.sql @@ -0,0 +1,16 @@ +-- # 8.2 + +-- # General Rules: 3, b: +-- # [E021-12] +VALUES ROW(1, 'hello' = 'hello '); +VALUES ROW(2, 'hello ' = 'hello'); +VALUES ROW(3, 'hello ' = 'hello '); +VALUES ROW(4, ' hello' = 'hello'); +VALUES ROW(5, 'hello' = ' hello'); +VALUES ROW(6, ' hello' = ' hello'); +-- COL1: 1 COL2: TRUE +-- COL1: 2 COL2: TRUE +-- COL1: 3 COL2: TRUE +-- COL1: 4 COL2: FALSE +-- COL1: 5 COL2: FALSE +-- COL1: 6 COL2: TRUE diff --git a/tests/syntax_tokens.sql b/tests/syntax_tokens.sql new file mode 100644 index 0000000..147aeac --- /dev/null +++ b/tests/syntax_tokens.sql @@ -0,0 +1,14 @@ +-- # There are some special tokens that are required to reduce the ambiguity for +-- # the parser. These tests ensure different whitespace combinations don't +-- # interfere with that. + +-- # Reducing: "." "*". We don't care about the errors, only ensure there is no +-- # syntax errors. +SELECT x, count(*) FROM foo; +SELECT x, count( * ) FROM foo; +SELECT x, count (*) FROM foo; +SELECT x, count ( *) FROM foo; +-- error 42P01: no such table: ":memory:".PUBLIC.FOO +-- error 42P01: no such table: ":memory:".PUBLIC.FOO +-- error 42P01: no such table: ":memory:".PUBLIC.FOO +-- error 42P01: no such table: ":memory:".PUBLIC.FOO diff --git a/tests/update.sql b/tests/update.sql index e3cf5c2..0f191bc 100644 --- a/tests/update.sql +++ b/tests/update.sql @@ -108,3 +108,15 @@ SELECT * FROM foo; -- msg: INSERT 1 -- error 22001: string data right truncation for CHARACTER VARYING(4) -- BAZ: abc + +-- # https://github.com/elliotchance/vsql/issues/200 +CREATE TABLE PrimaryProduct (id INT NOT NULL, PRIMARY KEY(id)); +INSERT INTO PrimaryProduct (id) VALUES (1); +EXPLAIN UPDATE PrimaryProduct SET id = 2 WHERE id = 1; +UPDATE PrimaryProduct SET id = 2 WHERE id = 1; +SELECT * FROM PrimaryProduct; +-- msg: CREATE TABLE 1 +-- msg: INSERT 1 +-- EXPLAIN: PRIMARY KEY ":memory:".PUBLIC.PRIMARYPRODUCT (ID INTEGER NOT NULL) BETWEEN 1 AND 1 +-- msg: UPDATE 1 +-- ID: 2 diff --git a/tests/values.sql b/tests/values.sql index 6de92c7..193433b 100644 --- a/tests/values.sql +++ b/tests/values.sql @@ -23,9 +23,9 @@ SELECT * FROM (VALUES 1, 'foo', TRUE); EXPLAIN SELECT * FROM (VALUES ROW(1, 'foo', TRUE)); -- EXPLAIN: $1: --- EXPLAIN: VALUES (COL1 SMALLINT, COL2 CHARACTER VARYING(3), COL3 BOOLEAN) = ROW(1, 'foo', TRUE) --- EXPLAIN: TABLE $1 (COL1 SMALLINT, COL2 CHARACTER VARYING(3), COL3 BOOLEAN) --- EXPLAIN: EXPR ($1.COL1 SMALLINT, $1.COL2 CHARACTER VARYING(3), $1.COL3 BOOLEAN) +-- EXPLAIN: VALUES (COL1 SMALLINT, COL2 CHARACTER(3), COL3 BOOLEAN) = ROW(1, 'foo', TRUE) +-- EXPLAIN: TABLE $1 (COL1 SMALLINT, COL2 CHARACTER(3), COL3 BOOLEAN) +-- EXPLAIN: EXPR ($1.COL1 SMALLINT, $1.COL2 CHARACTER(3), $1.COL3 BOOLEAN) EXPLAIN SELECT * FROM (VALUES 1, 'foo', TRUE) AS t1 (abc, col2, "f"); -- EXPLAIN: T1: @@ -35,9 +35,9 @@ EXPLAIN SELECT * FROM (VALUES 1, 'foo', TRUE) AS t1 (abc, col2, "f"); EXPLAIN SELECT * FROM (VALUES ROW(1, 'foo', TRUE)) AS t1 (abc, col2, "f"); -- EXPLAIN: T1: --- EXPLAIN: VALUES (ABC SMALLINT, COL2 CHARACTER VARYING(3), "f" BOOLEAN) = ROW(1, 'foo', TRUE) --- EXPLAIN: TABLE T1 (ABC SMALLINT, COL2 CHARACTER VARYING(3), "f" BOOLEAN) --- EXPLAIN: EXPR (T1.ABC SMALLINT, T1.COL2 CHARACTER VARYING(3), T1."f" BOOLEAN) +-- EXPLAIN: VALUES (ABC SMALLINT, COL2 CHARACTER(3), "f" BOOLEAN) = ROW(1, 'foo', TRUE) +-- EXPLAIN: TABLE T1 (ABC SMALLINT, COL2 CHARACTER(3), "f" BOOLEAN) +-- EXPLAIN: EXPR (T1.ABC SMALLINT, T1.COL2 CHARACTER(3), T1."f" BOOLEAN) SELECT * FROM (VALUES 1, 'foo', TRUE) AS t1 (abc, col2, "f"); -- error 42601: syntax error: unknown column: T1.COL2 @@ -91,9 +91,9 @@ SELECT * FROM (VALUES ROW(123, 'hi'), ROW(456, 'there')); EXPLAIN SELECT * FROM (VALUES ROW(123, 'hi'), ROW(456, 'there')); -- EXPLAIN: $1: --- EXPLAIN: VALUES (COL1 SMALLINT, COL2 CHARACTER VARYING(2)) = ROW(123, 'hi'), ROW(456, 'there') --- EXPLAIN: TABLE $1 (COL1 SMALLINT, COL2 CHARACTER VARYING(2)) --- EXPLAIN: EXPR ($1.COL1 SMALLINT, $1.COL2 CHARACTER VARYING(2)) +-- EXPLAIN: VALUES (COL1 SMALLINT, COL2 CHARACTER(2)) = ROW(123, 'hi'), ROW(456, 'there') +-- EXPLAIN: TABLE $1 (COL1 SMALLINT, COL2 CHARACTER(2)) +-- EXPLAIN: EXPR ($1.COL1 SMALLINT, $1.COL2 CHARACTER(2)) SELECT * FROM (VALUES ROW(123, 'hi'), ROW(456, 'there')) AS foo (bar, baz); -- BAR: 123 BAZ: hi @@ -102,9 +102,9 @@ SELECT * FROM (VALUES ROW(123, 'hi'), ROW(456, 'there')) AS foo (bar, baz); EXPLAIN SELECT * FROM (VALUES ROW(123, 'hi'), ROW(456, 'there')) AS foo (bar, baz); -- EXPLAIN: FOO: --- EXPLAIN: VALUES (BAR SMALLINT, BAZ CHARACTER VARYING(2)) = ROW(123, 'hi'), ROW(456, 'there') --- EXPLAIN: TABLE FOO (BAR SMALLINT, BAZ CHARACTER VARYING(2)) --- EXPLAIN: EXPR (FOO.BAR SMALLINT, FOO.BAZ CHARACTER VARYING(2)) +-- EXPLAIN: VALUES (BAR SMALLINT, BAZ CHARACTER(2)) = ROW(123, 'hi'), ROW(456, 'there') +-- EXPLAIN: TABLE FOO (BAR SMALLINT, BAZ CHARACTER(2)) +-- EXPLAIN: EXPR (FOO.BAR SMALLINT, FOO.BAZ CHARACTER(2)) SELECT * FROM (VALUES 1, 2) AS t1 (foo); -- FOO: 1 diff --git a/tests/whitespace.sql b/tests/whitespace.sql index 84985f0..70bed6e 100644 --- a/tests/whitespace.sql +++ b/tests/whitespace.sql @@ -119,4 +119,4 @@ VALUES123; -- COL1: 123 VALUES123; --- error 42601: syntax error: near "VALUESa123" +-- error 42601: syntax error: unexpected identifier diff --git a/vsql/bench.v b/vsql/bench.v index 68b9bfb..599e318 100644 --- a/vsql/bench.v +++ b/vsql/bench.v @@ -25,10 +25,10 @@ pub mut: pub fn new_benchmark(conn &Connection) Benchmark { return Benchmark{ account_rows: 100000 - teller_rows: 10 - branch_rows: 1 - run_for: time.minute - conn: conn + teller_rows: 10 + branch_rows: 1 + run_for: time.minute + conn: conn } } diff --git a/vsql/btree.v b/vsql/btree.v index d0675d8..16ff54f 100644 --- a/vsql/btree.v +++ b/vsql/btree.v @@ -30,7 +30,7 @@ mut: fn new_btree(pager Pager, page_size int) &Btree { return &Btree{ - pager: pager + pager: pager page_size: page_size } } @@ -383,8 +383,8 @@ fn (mut p Btree) split_page(path []int, page Page, obj PageObject, kind u8) ! { fn (p Btree) new_range_iterator(min []u8, max []u8) PageIterator { return PageIterator{ btree: p - min: min - max: max + min: min + max: max } } diff --git a/vsql/btree_test.v b/vsql/btree_test.v index 3cbf1dd..858ee6a 100644 --- a/vsql/btree_test.v +++ b/vsql/btree_test.v @@ -29,7 +29,7 @@ fn test_btree_test() ! { page_size := 256 file_name := 'btree.vsql' - for tt in 0 .. vsql.times { + for tt in 0 .. times { for size := 1; size <= 1000; size *= 10 { for blob_size in blob_sizes { if os.exists(file_name) { diff --git a/vsql/compiler.v b/vsql/compiler.v index 7e59110..8bf60bd 100644 --- a/vsql/compiler.v +++ b/vsql/compiler.v @@ -24,8 +24,8 @@ struct CompileResult { fn (c CompileResult) with_agg(contains_agg bool) CompileResult { return CompileResult{ - run: c.run - typ: c.typ + run: c.run + typ: c.typ contains_agg: contains_agg } } diff --git a/vsql/connection.v b/vsql/connection.v index 858e3aa..cfaf2c3 100644 --- a/vsql/connection.v +++ b/vsql/connection.v @@ -34,17 +34,15 @@ mut: catalogs map[string]&CatalogConnection // funcs only needs to be initialized once on open() funcs []Func - // query_cache is maintained over file reopens. - query_cache &QueryCache // cast_rules are use for CAST() (see cast.v) cast_rules map[string]CastFunc // unary_operators and binary_operators are for operators (see operators.v) unary_operators map[string]UnaryOperatorFunc binary_operators map[string]BinaryOperatorFunc - // current_schema is where to search for unquailified table names. It will + // current_schema is where to search for unqualified table names. It will // have an initial value of 'PUBLIC'. current_schema string - // current_catalog (also known as the database). It will have an inital value + // current_catalog (also known as the database). It will have an initial value // derived from the first database file loaded. current_catalog string pub mut: @@ -115,8 +113,8 @@ fn open_connection(path string, options ConnectionOptions) !&Connection { mut conn := &Connection{ query_cache: options.query_cache current_catalog: catalog_name - current_schema: vsql.default_schema_name - now: default_now + current_schema: default_schema_name + now: default_now } register_builtin_funcs(mut conn)! @@ -140,9 +138,9 @@ pub fn (mut conn Connection) add_catalog(catalog_name string, path string, optio catalog := &CatalogConnection{ catalog_name: catalog_name - path: path - storage: new_storage(btree) - options: options + path: path + storage: new_storage(btree) + options: options } conn.catalogs[catalog_name] = catalog @@ -244,14 +242,27 @@ fn (mut conn CatalogConnection) release_read_connection() { // with different provided parameters. pub fn (mut conn Connection) prepare(sql_stmt string) !PreparedStmt { t := start_timer() - stmt, params, explain := conn.query_cache.parse(sql_stmt) or { - mut catalog := conn.catalog() - catalog.storage.transaction_aborted() - return err + + mut tokens := tokenize(sql_stmt) + + // EXPLAIN is super helpful, but not part of the SQL standard so we only + // treat it as a prefix that is trimmed off before parsing. + mut explain := false + if tokens[0].sym.v is IdentifierChain { + if tokens[0].sym.v.identifier.to_upper() == 'EXPLAIN' { + explain = true + tokens = tokens[1..].clone() + } } + + mut lexer := Lexer{tokens, 0} + mut parser := yy_new_parser() + parser.parse(mut lexer)! + + stmt := (parser as YYParserImpl).lval.v as Stmt elapsed_parse := t.elapsed() - return PreparedStmt{stmt, params, explain, &conn, elapsed_parse} + return PreparedStmt{stmt, map[string]Value{}, explain, &conn, elapsed_parse} } // query executes a statement. If there is a result set it will be returned. @@ -344,15 +355,18 @@ pub fn (mut conn Connection) register_function(prototype string, func fn ([]Valu pub fn (mut conn Connection) register_virtual_table(create_table string, data VirtualTableProviderFn) ! { // Registering virtual tables does not need use query cache. mut tokens := tokenize(create_table) - stmt := parse(tokens)! + mut lexer := Lexer{tokens, 0} + mut parser := yy_new_parser() + parser.parse(mut lexer)! + stmt := (parser as YYParserImpl).lval.v as Stmt if stmt is TableDefinition { mut table_name := conn.resolve_schema_identifier(stmt.table_name)! conn.catalogs[conn.current_catalog].virtual_tables[table_name.storage_id()] = VirtualTable{ - create_table_sql: create_table + create_table_sql: create_table create_table_stmt: stmt - data: data + data: data } return @@ -416,19 +430,20 @@ pub fn (mut conn CatalogConnection) schema_tables(schema string) ![]Table { // canonical (fully qualified) form. fn (conn Connection) resolve_identifier(identifier Identifier) Identifier { return Identifier{ - custom_id: identifier.custom_id - custom_typ: identifier.custom_typ - catalog_name: if identifier.catalog_name == '' && !identifier.entity_name.starts_with('$') { + custom_id: identifier.custom_id + custom_typ: identifier.custom_typ + catalog_name: if identifier.catalog_name == '' + && !identifier.entity_name.starts_with('$') { conn.current_catalog } else { identifier.catalog_name } - schema_name: if identifier.schema_name == '' && !identifier.entity_name.starts_with('$') { + schema_name: if identifier.schema_name == '' && !identifier.entity_name.starts_with('$') { conn.current_schema } else { identifier.schema_name } - entity_name: identifier.entity_name + entity_name: identifier.entity_name sub_entity_name: identifier.sub_entity_name } } @@ -480,14 +495,6 @@ fn (mut conn Connection) resolve_table_identifier(identifier Identifier, allow_v // default_connection_options() as a starting point and modify the attributes. pub struct ConnectionOptions { pub mut: - // query_cache contains the precompiled prepared statements that can be - // reused. This makes execution much faster as parsing the SQL is extremely - // expensive. - // - // By default each connection will be given its own query cache. However, - // you can safely share a single cache over multiple connections and you are - // encouraged to do so. - query_cache &QueryCache = unsafe { nil } // Warning: This only works for :memory: databases. Configuring it for // file-based databases will either be ignored or causes crashes. page_size int diff --git a/vsql/connection_test.v b/vsql/connection_test.v index ec6110f..0690ecd 100644 --- a/vsql/connection_test.v +++ b/vsql/connection_test.v @@ -24,11 +24,11 @@ fn test_concurrent_writes() ! { waits << spawn fn (file_name string, i int, options ConnectionOptions) { mut db := open_database(file_name, options) or { panic(err) } for j in 0 .. 100 { - if vsql.verbose { + if verbose { println('${i}.${j}: INSERT start') } db.query('INSERT INTO foo (x) VALUES (1)') or { panic(err) } - if vsql.verbose { + if verbose { println('${i}.${j}: INSERT done') } } diff --git a/vsql/earley.v b/vsql/earley.v index b97a0d5..20455e0 100644 --- a/vsql/earley.v +++ b/vsql/earley.v @@ -63,12 +63,12 @@ fn new_earley_state(name string, production &EarleyProduction, dot_index int, st } return &EarleyState{ - name: name - production: production + name: name + production: production start_column: start_column - dot_index: dot_index - rules: rules - end_column: unsafe { 0 } + dot_index: dot_index + rules: rules + end_column: unsafe { 0 } } } @@ -136,9 +136,9 @@ mut: fn new_earley_column(index int, token string, value string) &EarleyColumn { return &EarleyColumn{ - index: index - token: token - value: value + index: index + token: token + value: value unique: &Set{} } } diff --git a/vsql/grammar.y b/vsql/grammar.y new file mode 100644 index 0000000..f6a4ba5 --- /dev/null +++ b/vsql/grammar.y @@ -0,0 +1,695 @@ +%{ + +module vsql + +import math + +%} + +// non-reserved words +%token A +%token ABSOLUTE +%token ACTION +%token ADA +%token ADD +%token ADMIN +%token AFTER +%token ALWAYS +%token ASC +%token ASSERTION +%token ASSIGNMENT +%token ATTRIBUTE +%token ATTRIBUTES +%token BEFORE +%token BERNOULLI +%token BREADTH +%token C +%token CASCADE +%token CATALOG +%token CATALOG_NAME +%token CHAIN +%token CHAINING +%token CHARACTER_SET_CATALOG +%token CHARACTER_SET_NAME +%token CHARACTER_SET_SCHEMA +%token CHARACTERISTICS +%token CHARACTERS +%token CLASS_ORIGIN +%token COBOL +%token COLLATION +%token COLLATION_CATALOG +%token COLLATION_NAME +%token COLLATION_SCHEMA +%token COLUMNS +%token COLUMN_NAME +%token COMMAND_FUNCTION +%token COMMAND_FUNCTION_CODE +%token COMMITTED +%token CONDITIONAL +%token CONDITION_NUMBER +%token CONNECTION +%token CONNECTION_NAME +%token CONSTRAINT_CATALOG +%token CONSTRAINT_NAME +%token CONSTRAINT_SCHEMA +%token CONSTRAINTS +%token CONSTRUCTOR +%token CONTINUE +%token CURSOR_NAME +%token DATA +%token DATETIME_INTERVAL_CODE +%token DATETIME_INTERVAL_PRECISION +%token DEFAULTS +%token DEFERRABLE +%token DEFERRED +%token DEFINED +%token DEFINER +%token DEGREE +%token DEPTH +%token DERIVED +%token DESC +%token DESCRIBE_CATALOG +%token DESCRIBE_NAME +%token DESCRIBE_PROCEDURE_SPECIFIC_CATALOG +%token DESCRIBE_PROCEDURE_SPECIFIC_NAME +%token DESCRIBE_PROCEDURE_SPECIFIC_SCHEMA +%token DESCRIBE_SCHEMA +%token DESCRIPTOR +%token DIAGNOSTICS +%token DISPATCH +%token DOMAIN +%token DYNAMIC_FUNCTION +%token DYNAMIC_FUNCTION_CODE +%token ENCODING +%token ENFORCED +%token ERROR +%token EXCLUDE +%token EXCLUDING +%token EXPRESSION +%token FINAL +%token FINISH +%token FINISH_CATALOG +%token FINISH_NAME +%token FINISH_PROCEDURE_SPECIFIC_CATALOG +%token FINISH_PROCEDURE_SPECIFIC_NAME +%token FINISH_PROCEDURE_SPECIFIC_SCHEMA +%token FINISH_SCHEMA +%token FIRST +%token FLAG +%token FOLLOWING +%token FORMAT +%token FORTRAN +%token FOUND +%token FULFILL +%token FULFILL_CATALOG +%token FULFILL_NAME +%token FULFILL_PROCEDURE_SPECIFIC_CATALOG +%token FULFILL_PROCEDURE_SPECIFIC_NAME +%token FULFILL_PROCEDURE_SPECIFIC_SCHEMA +%token FULFILL_SCHEMA +%token G +%token GENERAL +%token GENERATED +%token GO +%token GOTO +%token GRANTED +%token HAS_PASS_THROUGH_COLUMNS +%token HAS_PASS_THRU_COLS +%token HIERARCHY +%token IGNORE +%token IMMEDIATE +%token IMMEDIATELY +%token IMPLEMENTATION +%token INCLUDING +%token INCREMENT +%token INITIALLY +%token INPUT +%token INSTANCE +%token INSTANTIABLE +%token INSTEAD +%token INVOKER +%token ISOLATION +%token IS_PRUNABLE +%token JSON +%token K +%token KEEP +%token KEY +%token KEYS +%token KEY_MEMBER +%token KEY_TYPE +%token LAST +%token LENGTH +%token LEVEL +%token LOCATOR +%token M +%token MAP +%token MATCHED +%token MAXVALUE +%token MESSAGE_LENGTH +%token MESSAGE_OCTET_LENGTH +%token MESSAGE_TEXT +%token MINVALUE +%token MORE +%token MUMPS +%token NAME +%token NAMES +%token NESTED +%token NESTING +%token NEXT +%token NFC +%token NFD +%token NFKC +%token NFKD +%token NORMALIZED +%token NULLABLE +%token NULLS +%token NUMBER +%token OBJECT +%token OCTETS +%token OPTION +%token OPTIONS +%token ORDERING +%token ORDINALITY +%token OTHERS +%token OUTPUT +%token OVERFLOW +%token OVERRIDING +%token P +%token PAD +%token PARAMETER_MODE +%token PARAMETER_NAME +%token PARAMETER_ORDINAL_POSITION +%token PARAMETER_SPECIFIC_CATALOG +%token PARAMETER_SPECIFIC_NAME +%token PARAMETER_SPECIFIC_SCHEMA +%token PARTIAL +%token PASCAL +%token PASS +%token PASSING +%token PAST +%token PATH +%token PLACING +%token PLAN +%token PLI +%token PRECEDING +%token PRESERVE +%token PRIOR +%token PRIVATE +%token PRIVATE_PARAMETERS +%token PRIVATE_PARAMS_S +%token PRIVILEGES +%token PRUNE +%token PUBLIC +%token QUOTES +%token READ +%token RELATIVE +%token REPEATABLE +%token RESPECT +%token RESTART +%token RESTRICT +%token RETURNED_CARDINALITY +%token RETURNED_LENGTH +%token RETURNED_OCTET_LENGTH +%token RETURNED_SQLSTATE +%token RETURNING +%token RETURNS_ONLY_PASS_THROUGH +%token RET_ONLY_PASS_THRU +%token ROLE +%token ROUTINE +%token ROUTINE_CATALOG +%token ROUTINE_NAME +%token ROUTINE_SCHEMA +%token ROW_COUNT +%token SCALAR +%token SCALE +%token SCHEMA +%token SCHEMA_NAME +%token SCOPE_CATALOG +%token SCOPE_NAME +%token SCOPE_SCHEMA +%token SECTION +%token SECURITY +%token SELF +%token SEQUENCE +%token SERIALIZABLE +%token SERVER_NAME +%token SESSION +%token SETS +%token SIMPLE +%token SIZE +%token SOURCE +%token SPACE +%token SPECIFIC_NAME +%token START_CATALOG +%token START_NAME +%token START_PROCEDURE_SPECIFIC_CATALOG +%token START_PROCEDURE_SPECIFIC_NAME +%token START_PROCEDURE_SPECIFIC_SCHEMA +%token START_SCHEMA +%token STATE +%token STATEMENT +%token STRING +%token STRUCTURE +%token STYLE +%token SUBCLASS_ORIGIN +%token T +%token TABLE_NAME +%token TABLE_SEMANTICS +%token TEMPORARY +%token THROUGH +%token TIES +%token TOP_LEVEL_COUNT +%token TRANSACTION +%token TRANSACTION_ACTIVE +%token TRANSACTIONS_COMMITTED +%token TRANSACTIONS_ROLLED_BACK +%token TRANSFORM +%token TRANSFORMS +%token TRIGGER_CATALOG +%token TRIGGER_NAME +%token TRIGGER_SCHEMA +%token TYPE +%token UNBOUNDED +%token UNCOMMITTED +%token UNCONDITIONAL +%token UNDER +%token UNNAMED +%token USAGE +%token USER_DEFINED_TYPE_CATALOG +%token USER_DEFINED_TYPE_CODE +%token USER_DEFINED_TYPE_NAME +%token USER_DEFINED_TYPE_SCHEMA +%token UTF16 +%token UTF32 +%token UTF8 +%token VIEW +%token WORK +%token WRAPPER +%token WRITE +%token ZONE + +// reserved words +%token ABS +%token ACOS +%token ALL +%token ALLOCATE +%token ALTER +%token AND +%token ANY +%token ARE +%token ARRAY +%token ARRAY_AGG +%token ARRAY_MAX_CARDINALITY +%token AS +%token ASENSITIVE +%token ASIN +%token ASYMMETRIC +%token AT +%token ATAN +%token ATOMIC +%token AUTHORIZATION +%token AVG +%token BEGIN +%token BEGIN_FRAME +%token BEGIN_PARTITION +%token BETWEEN +%token BIGINT +%token BINARY +%token BLOB +%token BOOLEAN +%token BOTH +%token BY +%token CALL +%token CALLED +%token CARDINALITY +%token CASCADED +%token CASE +%token CAST +%token CEIL +%token CEILING +%token CHAR +%token CHAR_LENGTH +%token CHARACTER +%token CHARACTER_LENGTH +%token CHECK +%token CLASSIFIER +%token CLOB +%token CLOSE +%token COALESCE +%token COLLATE +%token COLLECT +%token COLUMN +%token COMMIT +%token CONDITION +%token CONNECT +%token CONSTRAINT +%token CONTAINS +%token CONVERT +%token COPY +%token CORR +%token CORRESPONDING +%token COS +%token COSH +%token COUNT +%token COVAR_POP +%token COVAR_SAMP +%token CREATE +%token CROSS +%token CUBE +%token CUME_DIST +%token CURRENT +%token CURRENT_CATALOG +%token CURRENT_DATE +%token CURRENT_DEFAULT_TRANSFORM_GROUP +%token CURRENT_PATH +%token CURRENT_ROLE +%token CURRENT_ROW +%token CURRENT_SCHEMA +%token CURRENT_TIME +%token CURRENT_TIMESTAMP +%token CURRENT_PATH +%token CURRENT_ROLE +%token CURRENT_TRANSFORM_GROUP_FOR_TYPE +%token CURRENT_USER +%token CURSOR +%token CYCLE +%token DATE +%token DAY +%token DEALLOCATE +%token DEC +%token DECIMAL +%token DECFLOAT +%token DECLARE +%token DEFAULT +%token DEFINE +%token DELETE +%token DENSE_RANK +%token DEREF +%token DESCRIBE +%token DETERMINISTIC +%token DISCONNECT +%token DISTINCT +%token DOUBLE +%token DROP +%token DYNAMIC +%token EACH +%token ELEMENT +%token ELSE +%token EMPTY +%token END +%token END_FRAME +%token END_PARTITION +//%token END-EXEC +%token EQUALS +%token ESCAPE +%token EVERY +%token EXCEPT +%token EXEC +%token EXECUTE +%token EXISTS +%token EXP +%token EXTERNAL +%token EXTRACT +%token FALSE +%token FETCH +%token FILTER +%token FIRST_VALUE +%token FLOAT +%token FLOOR +%token FOR +%token FOREIGN +%token FRAME_ROW +%token FREE +%token FROM +%token FULL +%token FUNCTION +%token FUSION +%token GET +%token GLOBAL +%token GRANT +%token GROUP +%token GROUPING +%token GROUPS +%token HAVING +%token HOLD +%token HOUR +%token IDENTITY +%token IN +%token INDICATOR +%token INITIAL +%token INNER +%token INOUT +%token INSENSITIVE +%token INSERT +%token INT +%token INTEGER +%token INTERSECT +%token INTERSECTION +%token INTERVAL +%token INTO +%token IS +%token JOIN +%token JSON_ARRAY +%token JSON_ARRAYAGG +%token JSON_EXISTS +%token JSON_OBJECT +%token JSON_OBJECTAGG +%token JSON_QUERY +%token JSON_TABLE +%token JSON_TABLE_PRIMITIVE +%token JSON_VALUE +%token LAG +%token LANGUAGE +%token LARGE +%token LAST_VALUE +%token LATERAL +%token LEAD +%token LEADING +%token LEFT +%token LIKE +%token LIKE_REGEX +%token LISTAGG +%token LN +%token LOCAL +%token LOCALTIME +%token LOCALTIMESTAMP +%token LOG +%token LOG10 +%token LOWER +%token MATCH +%token MATCH_NUMBER +%token MATCH_RECOGNIZE +%token MATCHES +%token MAX +%token MEMBER +%token MERGE +%token METHOD +%token MIN +%token MINUTE +%token MOD +%token MODIFIES +%token MODULE +%token MONTH +%token MULTISET +%token NATIONAL +%token NATURAL +%token NCHAR +%token NCLOB +%token NEW +%token NO +%token NONE +%token NORMALIZE +%token NOT +%token NTH_VALUE +%token NTILE +%token NULL +%token NULLIF +%token NUMERIC +%token OCTET_LENGTH +%token OCCURRENCES_REGEX +%token OF +%token OFFSET +%token OLD +%token OMIT +%token ON +%token ONE +%token ONLY +%token OPEN +%token OR +%token ORDER +%token OUT +%token OUTER +%token OVER +%token OVERLAPS +%token OVERLAY +%token PARAMETER +%token PARTITION +%token PATTERN +%token PER +%token PERCENT +%token PERCENT_RANK +%token PERCENTILE_CONT +%token PERCENTILE_DISC +%token PERIOD +%token PORTION +%token POSITION +%token POSITION_REGEX +%token POWER +%token PRECEDES +%token PRECISION +%token PREPARE +%token PRIMARY +%token PROCEDURE +%token PTF +%token RANGE +%token RANK +%token READS +%token REAL +%token RECURSIVE +%token REF +%token REFERENCES +%token REFERENCING +%token REGR_AVGX +%token REGR_AVGY +%token REGR_COUNT +%token REGR_INTERCEPT +%token REGR_R2 +%token REGR_SLOPE +%token REGR_SXX +%token REGR_SXY +%token REGR_SYY +%token RELEASE +%token RESULT +%token RETURN +%token RETURNS +%token REVOKE +%token RIGHT +%token ROLLBACK +%token ROLLUP +%token ROW +%token ROW_NUMBER +%token ROWS +%token RUNNING +%token SAVEPOINT +%token SCOPE +%token SCROLL +%token SEARCH +%token SECOND +%token SEEK +%token SELECT +%token SENSITIVE +%token SESSION_USER +%token SET +%token SHOW +%token SIMILAR +%token SIN +%token SINH +%token SKIP +%token SMALLINT +%token SOME +%token SPECIFIC +%token SPECIFICTYPE +%token SQL +%token SQLEXCEPTION +%token SQLSTATE +%token SQLWARNING +%token SQRT +%token START +%token STATIC +%token STDDEV_POP +%token STDDEV_SAMP +%token SUBMULTISET +%token SUBSET +%token SUBSTRING +%token SUBSTRING_REGEX +%token SUCCEEDS +%token SUM +%token SYMMETRIC +%token SYSTEM +%token SYSTEM_TIME +%token SYSTEM_USER +%token TABLE +%token TABLESAMPLE +%token TAN +%token TANH +%token THEN +%token TIME +%token TIMESTAMP +%token TIMEZONE_HOUR +%token TIMEZONE_MINUTE +%token TO +%token TRAILING +%token TRANSLATE +%token TRANSLATE_REGEX +%token TRANSLATION +%token TREAT +%token TRIGGER +%token TRIM +%token TRIM_ARRAY +%token TRUE +%token TRUNCATE +%token UESCAPE +%token UNION +%token UNIQUE +%token UNKNOWN +%token UNNEST +%token UPDATE +%token UPPER +%token USER +%token USING +%token VALUE +%token VALUES +%token VALUE_OF +%token VAR_POP +%token VAR_SAMP +%token VARBINARY +%token VARCHAR +%token VARYING +%token VERSIONING +%token WHEN +%token WHENEVER +%token WHERE +%token WIDTH_BUCKET +%token WINDOW +%token WITH +%token WITHIN +%token WITHOUT +%token YEAR + +// operators +%token OPERATOR_EQUALS OPERATOR_LEFT_PAREN OPERATOR_RIGHT_PAREN; +%token OPERATOR_ASTERISK OPERATOR_COMMA OPERATOR_PLUS OPERATOR_MINUS; +%token OPERATOR_PERIOD OPERATOR_SOLIDUS OPERATOR_COLON OPERATOR_LESS_THAN; +%token OPERATOR_GREATER_THAN OPERATOR_DOUBLE_PIPE OPERATOR_NOT_EQUALS; +%token OPERATOR_GREATER_EQUALS OPERATOR_LESS_EQUALS OPERATOR_SEMICOLON; + +%token OPERATOR_PERIOD_ASTERISK OPERATOR_LEFT_PAREN_ASTERISK; + +// literals +%token LITERAL_IDENTIFIER LITERAL_STRING LITERAL_NUMBER; +%token E; + +// pseudo keywords +%token IS_TRUE IS_FALSE IS_UNKNOWN IS_NOT_TRUE IS_NOT_FALSE IS_NOT_UNKNOWN; + +%start start; + +%% + +// This is a special case that uses `yyrcvr.lval` to make sure the parser +// captures the final result. +start: + preparable_statement { yyrcvr.lval.v = $1.v as Stmt } + +// This is from 6.35. I have no idea why this needs to be placed higher than +// other rules, but without doing this it will report that "datetime_primary" +// cannot be reduced. +datetime_primary: + value_expression_primary { + $$.v = DatetimePrimary($1.v as ValueExpressionPrimary) + } +| datetime_value_function { + $$.v = DatetimePrimary($1.v as DatetimeValueFunction) + } + +%% diff --git a/vsql/header.v b/vsql/header.v index fddc851..c164921 100644 --- a/vsql/header.v +++ b/vsql/header.v @@ -49,10 +49,10 @@ mut: fn new_header(page_size int) Header { return Header{ // Set all default here - even if they are zero - to be explicit. - version: vsql.current_version + version: current_version schema_version: 0 - page_size: page_size - root_page: 0 + page_size: page_size + root_page: 0 // 2 is a reserved number for freezing rows, but the transaction_is is // always incremented before returning the next value. transaction_id: 2 @@ -73,8 +73,8 @@ fn read_header(mut file os.File) !Header { header := file.read_raw[Header]() or { return error('unable to read raw header: ${err}') } // Check file version compatibility. - if header.version != vsql.current_version { - return error('need version ${vsql.current_version} but database is ${header.version}') + if header.version != current_version { + return error('need version ${current_version} but database is ${header.version}') } return header diff --git a/vsql/lexer.v b/vsql/lexer.v index fc15be0..eba8d74 100644 --- a/vsql/lexer.v +++ b/vsql/lexer.v @@ -6,33 +6,32 @@ module vsql // Except for the eof and the keywords, the other tokens use the names described // in the SQL standard. enum TokenKind { - asterisk // ::= * - colon // ::= : - comma // ::= , - concatenation_operator // ::= || - equals_operator // ::= = - greater_than_operator // ::= > + asterisk // ::= * + colon // ::= : + comma // ::= , + concatenation_operator // ::= || + equals_operator // ::= = + greater_than_operator // ::= > greater_than_or_equals_operator // ::= >= keyword - left_paren // ::= ( - less_than_operator // ::= < + left_paren // ::= ( + less_than_operator // ::= < less_than_or_equals_operator // ::= <= - literal_identifier // foo or "foo" (delimited) - literal_number // 123 - literal_string // 'hello' - minus_sign // ::= - - not_equals_operator // ::= <> - period // ::= . - plus_sign // ::= + - right_paren // ::= ) - semicolon // ::= ; - solidus // ::= / + literal_identifier // foo or "foo" (delimited) + literal_number // 123 + literal_string // 'hello' + minus_sign // ::= - + not_equals_operator // ::= <> + period // ::= . + plus_sign // ::= + + right_paren // ::= ) + semicolon // ::= ; + solidus // ::= / } struct Token { -pub: - kind TokenKind - value string + token int + sym YYSymType } fn tokenize(sql_stmt string) []Token { @@ -40,7 +39,7 @@ fn tokenize(sql_stmt string) []Token { cs := sql_stmt.trim(';').runes() mut i := 0 - next: for i < cs.len { + for i < cs.len { // Numbers if cs[i] >= `0` && cs[i] <= `9` { mut word := '' @@ -48,18 +47,22 @@ fn tokenize(sql_stmt string) []Token { word += '${cs[i]}' i++ } - tokens << Token{.literal_number, word} + tokens << Token{token_literal_number, YYSymType{ + v: word + }} // There is a special case for approximate numbers where 'E' is considered // a separate token in the SQL BNF. However, "e2" should not be treated as // two tokens, but rather we need to catch this case only when with a // number token. if i < cs.len && (cs[i] == `e` || cs[i] == `E`) { - tokens << Token{.keyword, 'E'} + tokens << Token{token_e, YYSymType{}} i++ } - continue + unsafe { + goto next + } } // Strings @@ -71,8 +74,12 @@ fn tokenize(sql_stmt string) []Token { i++ } i++ - tokens << Token{.literal_string, word} - continue + tokens << Token{token_literal_string, YYSymType{ + v: new_character_value(word) + }} + unsafe { + goto next + } } // Delimited identifiers @@ -84,45 +91,59 @@ fn tokenize(sql_stmt string) []Token { i++ } i++ - tokens << Token{.literal_identifier, '"${word}"'} - continue + tokens << Token{token_literal_identifier, YYSymType{ + v: IdentifierChain{ + identifier: '"${word}"' + } + }} + unsafe { + goto next + } } // Operators multi := { - '<>': TokenKind.not_equals_operator - '>=': TokenKind.greater_than_or_equals_operator - '<=': TokenKind.less_than_or_equals_operator - '||': TokenKind.concatenation_operator + '<>': token_operator_not_equals + '>=': token_operator_greater_equals + '<=': token_operator_less_equals + '||': token_operator_double_pipe } for op, tk in multi { - if cs[i] == op[0] && cs[i + 1] == op[1] { - tokens << Token{tk, op} + if i < cs.len - 1 && cs[i] == op[0] && cs[i + 1] == op[1] { + tokens << Token{tk, YYSymType{ + v: op + }} i += 2 - continue next + unsafe { + goto next + } } } single := { - `(`: TokenKind.left_paren - `)`: TokenKind.right_paren - `*`: TokenKind.asterisk - `+`: TokenKind.plus_sign - `,`: TokenKind.comma - `-`: TokenKind.minus_sign - `/`: TokenKind.solidus - `;`: TokenKind.semicolon - `<`: TokenKind.less_than_operator - `=`: TokenKind.equals_operator - `>`: TokenKind.greater_than_operator - `.`: TokenKind.period - `:`: TokenKind.colon + `(`: token_operator_left_paren + `)`: token_operator_right_paren + `*`: token_operator_asterisk + `+`: token_operator_plus + `,`: token_operator_comma + `-`: token_operator_minus + `/`: token_operator_solidus + `;`: token_operator_semicolon + `<`: token_operator_less_than + `=`: token_operator_equals + `>`: token_operator_greater_than + `.`: token_operator_period + `:`: token_operator_colon } for op, tk in single { if cs[i] == op { - tokens << Token{tk, op.str()} + tokens << Token{tk, YYSymType{ + v: op.str() + }} i++ - continue next + unsafe { + goto next + } } } @@ -140,10 +161,32 @@ fn tokenize(sql_stmt string) []Token { continue } - tokens << if is_key_word(word) { - Token{TokenKind.keyword, word.to_upper()} - } else { - Token{TokenKind.literal_identifier, word} + upper_word := word.to_upper() + mut found := false + for tok_pos, tok_name in yy_toknames { + if tok_name == upper_word { + tok_number := tok_pos + 57343 + tokens << Token{tok_number, YYSymType{ + v: upper_word + }} + found = true + break + } + } + + if !found { + tokens << Token{token_literal_identifier, YYSymType{ + v: IdentifierChain{ + identifier: word + } + }} + } + + next: + length, new_token := tail_substitution(tokens) + if length > 0 { + tokens = tokens[..tokens.len - length].clone() + tokens << Token{new_token, YYSymType{}} } } @@ -160,3 +203,39 @@ fn is_identifier_char(c rune, is_not_first bool) bool { return yes } + +fn tail_substitution(tokens []Token) (int, int) { + len := tokens.len + + if len > 2 && tokens[len - 2].token == token_operator_period + && tokens[len - 1].token == token_operator_asterisk { + return 2, token_operator_period_asterisk + } + if len > 2 && tokens[len - 2].token == token_operator_left_paren + && tokens[len - 1].token == token_operator_asterisk { + return 2, token_operator_left_paren_asterisk + } + if len > 2 && tokens[len - 2].token == token_is && tokens[len - 1].token == token_true { + return 2, token_is_true + } + if len > 2 && tokens[len - 2].token == token_is && tokens[len - 1].token == token_false { + return 2, token_is_false + } + if len > 2 && tokens[len - 2].token == token_is && tokens[len - 1].token == token_unknown { + return 2, token_is_unknown + } + if len > 3 && tokens[len - 3].token == token_is && tokens[len - 2].token == token_not + && tokens[len - 1].token == token_true { + return 3, token_is_not_true + } + if len > 3 && tokens[len - 3].token == token_is && tokens[len - 2].token == token_not + && tokens[len - 1].token == token_false { + return 3, token_is_not_false + } + if len > 3 && tokens[len - 3].token == token_is && tokens[len - 2].token == token_not + && tokens[len - 1].token == token_unknown { + return 3, token_is_not_unknown + } + + return 0, 0 +} diff --git a/vsql/numeric.v b/vsql/numeric.v index 16ed1c3..dcf8606 100644 --- a/vsql/numeric.v +++ b/vsql/numeric.v @@ -24,15 +24,15 @@ struct Numeric { fn new_numeric(typ Type, numerator big.Integer, denominator big.Integer) Numeric { return Numeric{ - typ: typ - numerator: numerator + typ: typ + numerator: numerator denominator: denominator } } fn new_null_numeric(typ Type) Numeric { return Numeric{ - typ: typ + typ: typ is_null: true } } @@ -110,18 +110,18 @@ fn (n Numeric) bytes() []u8 { mut buf := new_bytes([]u8{}) mut flags := u8(0) if n.is_null { - flags |= vsql.numeric_is_null + flags |= numeric_is_null } if n.is_zero() { - flags |= vsql.numeric_is_zero + flags |= numeric_is_zero } if n.is_negative() { - flags |= vsql.numeric_is_negative + flags |= numeric_is_negative } buf.write_u8(flags) // If the value is NULL or 0 we don't need to encode anything further. - if flags & vsql.numeric_is_null != 0 || flags & vsql.numeric_is_zero != 0 { + if flags & numeric_is_null != 0 || flags & numeric_is_zero != 0 { return buf.bytes() } @@ -140,11 +140,11 @@ fn new_numeric_from_bytes(typ Type, data []u8) Numeric { mut buf := new_bytes(data) flags := buf.read_u8() - if flags & vsql.numeric_is_null != 0 { + if flags & numeric_is_null != 0 { return new_null_numeric(typ) } - if flags & vsql.numeric_is_zero != 0 { + if flags & numeric_is_zero != 0 { return new_numeric(typ, big.zero_int, big.zero_int) } @@ -154,7 +154,7 @@ fn new_numeric_from_bytes(typ Type, data []u8) Numeric { denominator_len := buf.read_i16() denominator := big.integer_from_bytes(buf.read_u8s(denominator_len), big.IntegerConfig{}) - if flags & vsql.numeric_is_negative != 0 { + if flags & numeric_is_negative != 0 { numerator = numerator.neg() } @@ -214,9 +214,9 @@ fn (n Numeric) round(scale i16) Numeric { denominator := n.denominator / big.integer_from_int(10).pow(u32(-(scale - n.denominator.str().len) - 1)) return new_numeric(Type{ - typ: n.typ.typ - size: n.typ.size - scale: scale + typ: n.typ.typ + size: n.typ.size + scale: scale not_null: n.typ.not_null }, numerator, denominator) } diff --git a/vsql/orm.v b/vsql/orm.v new file mode 100644 index 0000000..b211217 --- /dev/null +++ b/vsql/orm.v @@ -0,0 +1,687 @@ +module vsql + +import orm +import time + +pub const varchar_default_len = 255 + +// Returns a string of the vsql type based on the v type index +fn vsql_type_from_v(typ int) !string { + return if typ == orm.type_idx['i8'] || typ == orm.type_idx['i16'] || typ == orm.type_idx['u8'] { + 'SMALLINT' + } else if typ == orm.type_idx['bool'] { + 'BOOLEAN' + } else if typ == orm.type_idx['int'] || typ == orm.type_idx['u16'] || typ == 8 { + 'INTEGER' + } else if typ == orm.type_idx['i64'] || typ == orm.type_idx['u32'] { + 'BIGINT' + } else if typ == orm.type_idx['f32'] { + 'REAL' + } else if typ == orm.type_idx['u64'] { + 'BIGINT' + } else if typ == orm.type_idx['f64'] { + 'DOUBLE PRECISION' + } else if typ == orm.type_idx['string'] { + 'VARCHAR(${varchar_default_len})' + } else if typ == orm.time_ { + 'TIMESTAMP(6) WITHOUT TIME ZONE' + } else if typ == -1 { + // -1 is a field with @[sql: serial] + 'INTEGER' + } else { + error('Unknown type ${typ}') + } +} + +// fn get_prepared_args(query_data []orm.QueryData) map[string]Value { +// mut mp := map[string]Value{} +// for i , q in query_data { +// // for f in q.fields { +// // type_idx := +// // } +// // for j, v in f.data { +// // mp[':${i}_${j}'] = v +// // } +// } +// return mp +// } + +// `query_converter_lite` converts a statement like `INSERT INTO Product (id, product_name, price) VALUES (:1, :2, :3)` to `INSERT INTO Product (id, product_name, price) VALUES (:id, :product_name, :price)` +fn query_converter_lite(query string, query_data []orm.QueryData) !string { + mut counter := 1 + mut new_query := query + for data in query_data { + for field in data.fields { + new_query = new_query.replace(':${counter}', ':${field}') + counter++ + } + } + return new_query +} + +fn query_converter(query string, query_data []orm.QueryData) !string { + mut counter := 1 + mut new_query := query + + for data in query_data { + vals := primitive_array_to_string_array(data.data) + for val in vals { + new_query = new_query.replace(':${counter}', val) + counter++ + } + } + + return new_query +} + +fn primitive_array_to_string_array(prims []orm.Primitive) []string { + mut values := prims.map(fn (p orm.Primitive) string { + match p { + orm.InfixType { + // TODO(elliotchance): Not sure what this is? + return '${p}' + } + time.Time { + // + return 'TIMESTAMP \'${p}\'' + // return '${p}' + } + orm.Null { + return 'NULL' + } + bool { + if p { + return 'TRUE' + } + + return 'FALSE' + } + string { + return '\'${p}\'' + } + f32 { + return '${p}' + } + f64 { + return '${p}' + } + i16 { + return '${p}' + } + i64 { + return '${p}' + } + i8 { + return '${p}' + } + int { + return '${p}' + } + u16 { + return '${p}' + } + u32 { + return '${p}' + } + u64 { + return '${p}' + } + u8 { + return '${p}' + } + } + }) + return values +} + +// select is used internally by V's ORM for processing `SELECT` queries +pub fn (mut db Connection) select(config orm.SelectConfig, data orm.QueryData, where orm.QueryData) ![][]orm.Primitive { + // 1. Create query and bind necessary data + // println(orm.type_idx) + mut query := orm.orm_select_gen(config, '', true, ':', 1, where) + query = query_converter(query, [data, where])! + rows := db.query(query)! + mut ret := [][]orm.Primitive{} + for row in rows { + mut row_primitives := []orm.Primitive{} + keys := row.data.keys() + for idx, key in keys { + prim := row.get(key)! + type_idx := config.types[idx] + + // check orm.type_idx for what these numbers are + if type_idx == 5 { + row_primitives << i8(prim.int_value()) + } else if type_idx == 2 { + row_primitives << time.parse(prim.string_value())! + } else if type_idx == 6 { + row_primitives << i16(prim.int_value()) + } else if type_idx == 8 { + row_primitives << int(prim.int_value()) + } else if type_idx == 9 { + row_primitives << prim.int_value() + } else if type_idx == 11 { + row_primitives << u8(prim.int_value()) + } else if type_idx == 12 { + row_primitives << u16(prim.int_value()) + } else if type_idx == 13 { + row_primitives << u32(prim.int_value()) + } else if type_idx == 14 { + row_primitives << u64(prim.int_value()) + } else if type_idx == 16 { + row_primitives << f32(prim.f64_value()) + } else if type_idx == 17 { + row_primitives << prim.f64_value() + } else if type_idx == 19 { + row_primitives << prim.bool_value() == .is_true + } else { + row_primitives << prim.string_value() + } + } + ret << row_primitives + } + // println(ret) + return ret +} + +fn serial_name(table string) string { + return '${table}_SERIAL' +} + +fn get_table_columns(mut db Connection, table string, data []orm.QueryData) !map[string]Value { + mut mp := map[string]Value{} + mut tbl := Table{} + mut cat := db.catalog() + tables := cat.schema_tables('PUBLIC') or { [] } + for t in tables { + if t.name.entity_name == table.to_upper() { + tbl = t + break + } + } + if tbl.name.entity_name != table.to_upper() { + return error('Table ${table} not found') + } + for d in data { + for i, f in d.fields { + for c in tbl.columns { + if c.name.sub_entity_name == f.to_upper() { + mp[f] = primitive_to_value(c.typ, d.data[i])! + } + } + } + } + + return mp +} + +// insert is used internally by V's ORM for processing `INSERT` queries +pub fn (mut db Connection) insert(table string, data orm.QueryData) ! { + // println(data) + // mut tbl := get_table_columns(mut db, table, [data]) or { return err } + // println(tbl) + + mut values := primitive_array_to_string_array(data.data) + if data.auto_fields.len > 1 { + return error('multiple AUTO fields are not supported') + } else if data.auto_fields.len == 1 { + // println(data) + values[data.auto_fields[0]] = 'NEXT VALUE FOR "${serial_name(table)}"' + } + mut nums := []string{} + for i, _ in data.fields { + nums << '${i}' + } + insert_sql := 'INSERT INTO ${table} (${data.fields.join(', ')}) VALUES (${values.join(', ')})' + println(insert_sql) + // println(tbl) + $if trace_vsql_orm ? { + eprintln('> vsql insert: ${query}') + } + db.query(insert_sql) or { return err } + // mut stmt := db.prepare(insert_sql) or { return err } + // stmt.query(tbl) or { return err } +} + +// update is used internally by V's ORM for processing `UPDATE` queries +pub fn (mut db Connection) update(table string, data orm.QueryData, where orm.QueryData) ! { + mut query, _ := orm.orm_stmt_gen(.sqlite, table, '', .update, true, ':', 1, data, + where) + + // values := get_table_columns(mut db, table, [data, where]) or { return err } + query = query_converter(query, [data, where])! + println(query) + + $if trace_vsql_orm ? { + eprintln('> vsql update: ${query}') + } + db.query(query) or { return err } + // mut stmt := db.prepare(query) or { return err } + // stmt.query(values) or { return err } +} + +// delete is used internally by V's ORM for processing `DELETE ` queries +pub fn (mut db Connection) delete(table string, where orm.QueryData) ! { + mut query, _ := orm.orm_stmt_gen(.sqlite, table, '', .delete, true, ':', 1, orm.QueryData{}, + where) + + query = query_converter(query, [where])! + // values := get_table_columns(mut db, table, [where]) or { return err } + + $if trace_vsql_orm ? { + eprintln('> vsql delete: ${query}') + } + db.query(query) or { return err } + // mut stmt := db.prepare(query) or { return err } + // stmt.query(values) or { return err } +} + +// `last_id` is used internally by V's ORM for post-processing `INSERT` queries +// TODO i dont think vsql supports this +pub fn (mut db Connection) last_id() int { + return 0 +} + +// create is used internally by V's ORM for processing table creation queries (DDL) +pub fn (mut db Connection) create(table string, fields []orm.TableField) ! { + check_for_not_supported(mut db, table, fields) or { return err } + mut new_table := table + if is_reserved_word(table) { + new_table = '"${table}"' + } + mut query := orm.orm_table_gen(new_table, '', true, 0, fields, vsql_type_from_v, false) or { + return err + } + // 'IF NOT EXISTS' is not supported in vsql, so we remove it + query = query.replace(' IF NOT EXISTS ', ' ') + // `TEXT` is not supported in vsql, so we replace it with `VARCHAR(255) if its somehow used` + query = query.replace('TEXT', 'VARCHAR(${varchar_default_len})') + + $if trace_vsql_orm ? { + eprintln('> vsql create: ${query}') + } + // println(query) + // println(table) + // println(fields) + db.query(query) or { return err } +} + +// drop is used internally by V's ORM for processing table destroying queries (DDL) +pub fn (mut db Connection) drop(table string) ! { + query := 'DROP TABLE ${table};' + $if trace_vsql_orm ? { + eprintln('> vsql drop: ${query}') + } + + db.query(query) or { return err } + + // // check to see if there is a SEQUENCE for the table (for the @[sql: 'serial'] attribute) + db.query('EXPLAIN DROP SEQUENCE "${serial_name(table)}"') or { return } + // if we have not returned then we can drop the sequence + db.query('DROP SEQUENCE "${serial_name(table)}"') or { return err } +} + +fn check_for_not_supported(mut db Connection, table string, fields []orm.TableField) ! { + for field in fields { + if field.typ == orm.enum_ { + return error('enum is not supported in vsql') + } + if is_reserved_word(field.name) { + return error('reserved word ${field.name} cannot be used as a field name at ${table}.${field.name}') + } + for attr in field.attrs { + if attr.name == 'sql' { + if attr.arg == 'serial' { + db.query('CREATE SEQUENCE "${serial_name(table)}"')! + } + if is_reserved_word(attr.arg) { + return error('${attr.arg} is a reserved word in vsql') + } + } + if attr.name == 'default' { + return error('default is not supported in vsql') + } + if attr.name == 'unique' { + return error('unique is not supported in vsql') + } + if attr.name == 'primary' { + eprintln('primary is supported, but currently will break delete queries') + // return error('primary is supported, but currently will break delete queries') + } + } + } +} + +// primitive_to_value returns the Value of a Primitive based on the intended +// destination type. Primitives are used by the ORM. +// +// It's important to note that while types may be compatible, they can still be +// out of range, such as assigning an overflowing integer value to SMALLINT. +fn primitive_to_value(typ Type, p orm.Primitive) !Value { + // The match should be exhaustive for typ and p so that we can make sure we + // cover all combinations now and in the future. + match p { + orm.Null { + // In standard SQL, NULL's must be typed. + return new_null_value(typ.typ) + } + bool { + match typ.typ { + .is_boolean { + return new_boolean_value(p) + } + else {} + } + } + f32, f64 { + match typ.typ { + .is_real { + return new_real_value(f32(p)) + } + .is_double_precision { + return new_double_precision_value(f64(p)) + } + else {} + } + } + i16, i8, u8 { + match typ.typ { + .is_smallint { + return new_smallint_value(i16(p)) + } + else {} + } + } + int, u16 { + match typ.typ { + .is_smallint { + return new_smallint_value(i16(p)) + } + .is_integer { + return new_integer_value(int(p)) + } + else {} + } + } + u32, i64 { + match typ.typ { + .is_bigint { + return new_bigint_value(i64(p)) + } + else {} + } + } + u64 { + match typ.typ { + .is_smallint { + return new_smallint_value(i16(p)) + } + else {} + } + } + string { + match typ.typ { + .is_varchar { + return new_varchar_value(p) + } + .is_numeric { + return new_numeric_value(p) + } + else {} + } + } + time.Time { + match typ.typ { + .is_timestamp_with_time_zone, .is_timestamp_without_time_zone { + return new_timestamp_value(p.str())! + } + else {} + } + } + orm.InfixType {} + } + + return error('cannot assign ${p} to ${typ}') +} + +fn serial_name(table string) string { + return '${table}_SERIAL' +} + +fn reformat_table_name(table string) string { + mut new_table := table + if is_reserved_word(table) { + new_table = '"${table}"' + } + return new_table +} + +fn get_table_values_map(mut db Connection, table string, data []orm.QueryData) !map[string]Value { + mut mp := map[string]Value{} + mut tbl := Table{} + mut cat := db.catalog() + tables := cat.schema_tables('PUBLIC') or { [] } + + for t in tables { + if t.name.entity_name == table.to_upper() { + tbl = t + break + } + } + if tbl.name.entity_name != table.to_upper() { + return error('Table ${table} not found') + } + mut field_counter := 1 + for d in data { + for i, f in d.fields { + for c in tbl.columns { + if c.name.sub_entity_name == f.to_upper() { + if mp.keys().contains(f) { + field_key := '${f}_${field_counter.str()}' + mp[field_key] = primitive_to_value(c.typ, d.data[i])! + field_counter++ + } else { + mp[f] = primitive_to_value(c.typ, d.data[i])! + } + } + } + } + } + + return mp +} + +// `query_reformatter` converts a statement like `INSERT INTO Product (id, product_name, price) VALUES (:1, :2, :3)` to `INSERT INTO Product (id, product_name, price) VALUES (:id, :product_name, :price)` +fn query_reformatter(query string, query_data []orm.QueryData) string { + mut counter := 1 + mut field_counter := 1 + mut new_query := query + for data in query_data { + for field in data.fields { + // this if check is for if there are multiple of the same field being checked for + if new_query.contains(':${field}') { + new_query = new_query.replace(':${counter}', ':${field}_${field_counter.str()}') + field_counter++ + counter++ + } else { + new_query = new_query.replace(':${counter}', ':${field}') + counter++ + } + } + } + return new_query +} + +fn check_for_not_supported(mut db Connection, table string, fields []orm.TableField) ! { + for field in fields { + if field.typ == orm.enum_ { + return error('enum is not supported in vsql') + } + if is_reserved_word(field.name) { + return error('reserved word ${field.name} cannot be used as a field name at ${table}.${field.name}') + } + for attr in field.attrs { + if attr.name == 'sql' { + if attr.arg == 'serial' { + db.query('CREATE SEQUENCE "${serial_name(table)}"')! + } + if is_reserved_word(attr.arg) { + return error('${attr.arg} is a reserved word in vsql') + } + } + if attr.name == 'default' { + return error('default is not supported in vsql') + } + if attr.name == 'unique' { + return error('unique is not supported in vsql') + } + } + } +} + +// select is used internally by V's ORM for processing `SELECT` queries +pub fn (mut db Connection) select(config orm.SelectConfig, data orm.QueryData, where orm.QueryData) ![][]orm.Primitive { + conf := orm.SelectConfig{ + ...config + table: reformat_table_name(config.table) + } + mut query := orm.orm_select_gen(conf, '', true, ':', 1, where) + query = query_reformatter(query, [data, where]) + mut rows := Result{} + if data.data.len == 0 && where.data.len == 0 { + rows = db.query(query) or { return err } + } else { + values := get_table_values_map(mut db, config.table, [data, where]) or { return err } + mut stmt := db.prepare(query) or { return err } + rows = stmt.query(values) or { return err } + } + + mut ret := [][]orm.Primitive{} + for row in rows { + mut row_primitives := []orm.Primitive{} + keys := row.data.keys() + for _, key in keys { + prim := row.get(key)! + row_primitives << prim.primitive() or { return err } + } + ret << row_primitives + } + + return ret +} + +// insert is used internally by V's ORM for processing `INSERT` queries +pub fn (mut db Connection) insert(table string, data orm.QueryData) ! { + new_table := reformat_table_name(table) + mut tbl := get_table_values_map(mut db, table, [data]) or { return err } + mut query := 'INSERT INTO ${new_table} (${data.fields.join(', ')}) VALUES (:${data.fields.join(', :')})' + // if a table has a serial field, we need to remove it from the insert statement + // only allows one serial field per table, as do most RDBMS + if data.auto_fields.len > 1 { + return error('multiple AUTO fields are not supported') + } else if data.auto_fields.len == 1 { + autofield_idx := data.auto_fields[0] + autofield := data.fields[autofield_idx] + tbl.delete(autofield) + query = query.replace(':${autofield}', 'NEXT VALUE FOR "${serial_name(table)}"') + } + mut stmt := db.prepare(query) or { return err } + stmt.query(tbl) or { return err } +} + +// update is used internally by V's ORM for processing `UPDATE` queries +pub fn (mut db Connection) update(table string, data orm.QueryData, where orm.QueryData) ! { + new_table := reformat_table_name(table) + mut query, _ := orm.orm_stmt_gen(.sqlite, new_table, '', .update, true, ':', 1, data, + where) + values := get_table_values_map(mut db, table, [data, where]) or { return err } + query = query_reformatter(query, [data, where]) + mut stmt := db.prepare(query) or { return err } + stmt.query(values) or { return err } +} + +// delete is used internally by V's ORM for processing `DELETE ` queries +pub fn (mut db Connection) delete(table string, where orm.QueryData) ! { + new_table := reformat_table_name(table) + mut query, _ := orm.orm_stmt_gen(.sqlite, new_table, '', .delete, true, ':', 1, orm.QueryData{}, + where) + query = query_reformatter(query, [where]) + values := get_table_values_map(mut db, table, [where]) or { return err } + mut stmt := db.prepare(query) or { return err } + stmt.query(values) or { return err } +} + +// `last_id` is used internally by V's ORM for post-processing `INSERT` queries +//
    TODO i dont think vsql supports this +pub fn (mut db Connection) last_id() int { + return 0 +} + +// create is used internally by V's ORM for processing table creation queries (DDL) +pub fn (mut db Connection) create(table string, fields []orm.TableField) ! { + check_for_not_supported(mut db, table, fields) or { return err } + new_table := reformat_table_name(table) + mut query := orm.orm_table_gen(new_table, '', true, 0, fields, vsql_type_from_v, false) or { + return err + } + // 'IF NOT EXISTS' is not supported in vsql, so we remove it + query = query.replace(' IF NOT EXISTS ', ' ') + // `TEXT` is not supported in vsql, so we replace it with `VARCHAR(255) if its somehow used` + query = query.replace('TEXT', 'VARCHAR(${varchar_default_len})') + db.query(query) or { return err } +} + +// drop is used internally by V's ORM for processing table destroying queries (DDL) +pub fn (mut db Connection) drop(table string) ! { + new_table := reformat_table_name(table) + mut query := 'DROP TABLE ${new_table};' + db.query(query) or { return err } + + // check to see if there is a SEQUENCE for the table (for the @[sql: 'serial'] attribute) + // if there is, drop it + mut cat := db.catalog() + seqs := cat.sequences('PUBLIC') or { [] } + for sequence in seqs { + if sequence.name.entity_name == serial_name(table).to_upper() { + query = 'DROP SEQUENCE "${serial_name(table)}"' + db.query(query) or { return err } + } + } +} + +// convert a vsql row value to orm.Primitive +fn (v Value) primitive() !orm.Primitive { + if v.is_null { + return orm.Null{} + } + return match v.typ.typ { + .is_boolean { + orm.Primitive(v.bool_value() == .is_true) + } + .is_smallint { + orm.Primitive(i16(v.int_value())) + } + .is_integer { + orm.Primitive(int(v.int_value())) + } + .is_bigint { + orm.Primitive(i64(v.int_value())) + } + .is_varchar, .is_character { + orm.Primitive(v.string_value()) + } + .is_real { + orm.Primitive(f32(v.f64_value())) + } + .is_double_precision { + orm.Primitive(v.f64_value()) + } + .is_decimal, .is_numeric { + orm.Primitive(v.str()) + } + .is_date, .is_time_with_time_zone, .is_timestamp_without_time_zone, + .is_timestamp_with_time_zone, .is_time_without_time_zone { + orm.Primitive(v.str()) + } + } +} diff --git a/vsql/orm_test.v b/vsql/orm_test.v new file mode 100644 index 0000000..a516915 --- /dev/null +++ b/vsql/orm_test.v @@ -0,0 +1,402 @@ +module vsql + +// Structs intentionally have less than 6 fields, any more then inserts queries get exponentially slower. +// https://github.com/elliotchance/vsql/issues/199 +import time + +// struct TestDateTypes { +// id int @[primary; sql: serial] +// custom1 string @[sql_type: 'TIME WITH TIME ZONE'] +// custom2 string @[sql_type: 'TIMESTAMP(3) WITH TIME ZONE'] +// custom3 string @[sql_type: 'INT'] +// custom4 string @[sql_type: 'DATE'] +// custom5 string @[sql_type: 'TIMESTAMP(3) WITHOUT TIME ZONE'] +// custom6 string @[sql_type: 'TIME WITHOUT TIME ZONE'] +// } + +@[table: 'testcustomtable'] +struct TestCustomTableAndSerial { + id int @[sql: serial] + an_bool bool +} + +struct TestPrimaryBroken { + id int @[primary; sql: serial] +} + +struct TestDefaultAttribute { + id int @[sql: serial] + name string + created_at string @[default: 'CURRENT_TIMESTAMP(6)'; sql_type: 'TIMESTAMP(3) WITHOUT TIME ZONE'] +} + +struct TestUniqueAttribute { + attribute string @[unique] +} + +struct TestReservedWordField { + where string +} + +struct TestReservedWordSqlAttribute { + ok string @[sql: 'ORDER'] +} + +struct TestOrmValuesOne { + an_f32 f32 // REAL + an_f64 f64 // DOUBLE PRECISION + an_i16 i16 // SMALLINT + an_i64 i64 // BIGINT +} + +struct TestOrmValuesTwo { + an_i8 i8 // SMALLINT + an_int int // INTEGER +<<<<<<< HEAD +<<<<<<< HEAD + a_string string // CHARACTER VARYING(255) / VARCHAR(255) +======= + a_string string // CHARACTER VARYING(255) +>>>>>>> d4105ba (v orm implementation with standard query) +======= + a_string string // CHARACTER VARYING(255) / VARCHAR(255) +>>>>>>> 7a9b776 (chore: remove unnecessary comments, add more descriptive comments) +} + +struct TestOrmValuesThree { + an_u16 u16 // INTEGER + an_u32 u32 // BIGINT + an_u64 u64 // BIGINT + an_u8 u8 // SMALLINT +} + +struct TestOrmValuesFour { + a_time time.Time // TIMESTAMP(6) WITH TIME ZONE + a_bool bool // BOOLEAN + int_or_null ?int // optional INTEGER +} + +// ORMTableEnum is not supported. +struct ORMTableEnum { + an_enum Colors +} + +enum Colors { + red + green + blue +} + +// @[table: 'GROUP'] +struct ORMTable2 { + dummy int +} + +struct Product { + id int + product_name string + price string @[sql_type: 'NUMERIC(5,2)'] + quantity ?i16 +} + +fn test_orm_create_table_with_reserved_word() { + mut db := open(':memory:')! + sql db { + create table ORMTable2 + }! + dumm := ORMTable2{dummy: 1} + sql db { + insert dumm into ORMTable2 + }! + + sql db { + drop table ORMTable2 + }! +} + +fn test_custom_table_name_and_serial_crud() { + mut db := open(':memory:')! + mut error := '' + sql db { + create table TestCustomTableAndSerial + } or { error = err.str() } + assert error == '' + + test_custom_sql := TestCustomTableAndSerial{ + an_bool: true + } + test_custom_sql2 := TestCustomTableAndSerial{ + an_bool: false + } + + sql db { + insert test_custom_sql into TestCustomTableAndSerial + insert test_custom_sql2 into TestCustomTableAndSerial + } or { error = err.str() } + + assert error == '' + mut all := sql db { + select from TestCustomTableAndSerial + }! + assert all[0].id != 0 + + sql db { + update TestCustomTableAndSerial set an_bool = false where id == all[0].id + } or { error = err.str() } + assert error == '' + + sql db { + delete from TestCustomTableAndSerial where id == all[1].id + } or { error = err.str() } + assert error == '' + + all = sql db { + select from TestCustomTableAndSerial + }! + assert all.len == 1 + assert all[0].an_bool == false + + sql db { + drop table TestCustomTableAndSerial + } or { error = err.str() } + + assert error == '' +} + +// fn test_reserved_words() { +// mut db := open(':memory:')! +// mut error := '' +// sql db { +// create table TestReservedWordField +// } or { error = err.str() } +// assert error == 'reserved word where cannot be used as a field name at TestReservedWordField.where' +// sql db { +// create table TestReservedWordSqlAttribute +// } or { error = err.str() } +// assert error == 'ORDER is a reserved word in vsql' +// } + +// fn test_unsupported_attributes() { +// mut db := open(':memory:')! +// mut error := '' +// sql db { +// create table TestDefaultAttribute +// } or { error = err.str() } +// assert error == 'default is not supported in vsql' +// sql db { +// create table TestUniqueAttribute +// } or { error = err.str() } +// assert error == 'unique is not supported in vsql' +// } + +// fn test_default_orm_values() { +// mut db := open(':memory:')! +// mut error := '' +// sql db { +// create table TestOrmValuesOne +// create table TestOrmValuesTwo +// create table TestOrmValuesThree +// create table TestOrmValuesFour +// } or { error = err.str() } +// assert error == '' + +// values_one := TestOrmValuesOne{ +// an_f32: 3.14 +// an_f64: 2.718281828459 +// an_i16: 12345 +// an_i64: 123456789012345 +// } +// values_two := TestOrmValuesTwo{ +// an_i8: 12 +// an_int: 123456 +// a_string: 'Hello, World!' +// } +// values_three := TestOrmValuesThree{ +// an_u16: 54321 +// an_u32: 1234567890 +// an_u64: 1234 +// an_u8: 255 +// } + +// values_four := TestOrmValuesFour{ +// a_time: time.now() +// a_bool: true +// // int_or_null: 123 +// } +// values_four_b := TestOrmValuesFour{ +// a_time: time.now() +// a_bool: false +// int_or_null: 123 +// } + +// sql db { +// insert values_one into TestOrmValuesOne +// insert values_two into TestOrmValuesTwo +// insert values_three into TestOrmValuesThree +// insert values_four into TestOrmValuesFour +// insert values_four_b into TestOrmValuesFour +// } or { error = err.str() } +// assert error == '' + +// result_values_one := sql db { +// select from TestOrmValuesOne +// }! +// one := result_values_one[0] + +// assert typeof(one.an_f32).idx == typeof[f32]().idx +// assert one.an_f32 == 3.14 +// assert typeof(one.an_f64).idx == typeof[f64]().idx +// assert one.an_f64 == 2.718281828459 +// assert typeof(one.an_i16).idx == typeof[i16]().idx +// assert one.an_i16 == 12345 +// assert typeof(one.an_i64).idx == typeof[i64]().idx +// assert one.an_i64 == 123456789012345 + +// result_values_two := sql db { +// select from TestOrmValuesTwo +// }! + +// two := result_values_two[0] + +// assert typeof(two.an_i8).idx == typeof[i8]().idx +// assert two.an_i8 == 12 +// assert typeof(two.an_int).idx == typeof[int]().idx +// assert two.an_int == 123456 +// assert typeof(two.a_string).idx == typeof[string]().idx +// assert two.a_string == 'Hello, World!' + +// result_values_three := sql db { +// select from TestOrmValuesThree +// }! + +// three := result_values_three[0] + +// assert typeof(three.an_u16).idx == typeof[u16]().idx +// assert three.an_u16 == 54321 +// assert typeof(three.an_u32).idx == typeof[u32]().idx +// assert three.an_u32 == 1234567890 +// assert typeof(three.an_u64).idx == typeof[u64]().idx +// assert three.an_u64 == 1234 +// assert typeof(three.an_u8).idx == typeof[u8]().idx +// assert three.an_u8 == 255 + +// result_values_four := sql db { +// select from TestOrmValuesFour +// }! + +// four := result_values_four[0] + +// assert typeof(four.a_time).idx == typeof[time.Time]().idx +// assert typeof(four.a_bool).idx == typeof[bool]().idx +// assert four.a_bool == true +// assert typeof(four.int_or_null).idx == typeof[?int]().idx +// unwrapped_option_one := four.int_or_null or { 0 } +// assert unwrapped_option_one == 0 +// unwrapped_option_two := result_values_four[1].int_or_null or { 0 } +// assert unwrapped_option_two == 123 +// } + +// fn test_orm_create_enum_is_not_supported() { +// mut db := open(':memory:')! +// mut error := '' +// sql db { +// create table ORMTableEnum +// } or { error = err.str() } +// assert error == 'enum is not supported in vsql' +// } + +// fn test_orm_select_where() { +// mut db := open(':memory:')! +// mut error := '' + +// sql db { +// create table Product +// } or { panic(err) } + +// prods := [ +// Product{1, 'Ice Cream', '5.99', 17}, +// Product{2, 'Ham Sandwhich', '3.47', none}, +// Product{3, 'Bagel', '1.25', 45}, +// ] +// for product in prods { +// sql db { +// insert product into Product +// } or { panic(err) } +// } +// mut products := sql db { +// select from Product where id == 2 +// }! + +// assert products == [Product{2, 'Ham Sandwhich', '3.47', none}] + +// products = sql db { +// select from Product where id == 5 +// }! + +// assert products == [] + +// products = sql db { +// select from Product where id != 3 +// }! + +// assert products == [Product{1, 'Ice Cream', '5.99', 17}, +// Product{2, 'Ham Sandwhich', '3.47', none}] + +// products = sql db { +// select from Product where price > '3.47' +// }! + +// assert products == [Product{1, 'Ice Cream', '5.99', 17}] + +// products = sql db { +// select from Product where price >= '3' +// }! + +// assert products == [Product{1, 'Ice Cream', '5.99', 17}, +// Product{2, 'Ham Sandwhich', '3.47', none}] + +// products = sql db { +// select from Product where price < '3.47' +// }! + +// assert products == [Product{3, 'Bagel', '1.25', 45}] + +// products = sql db { +// select from Product where price <= '5' +// }! + +// assert products == [Product{2, 'Ham Sandwhich', '3.47', none}, +// Product{3, 'Bagel', '1.25', 45}] + +// // TODO (daniel-le97): The ORM does not support a "not like" constraint right now. + +// products = sql db { +// select from Product where product_name like 'Ham%' +// }! + +// assert products == [Product{2, 'Ham Sandwhich', '3.47', none}] + +// products = sql db { +// select from Product where quantity is none +// }! + +// assert products == [Product{2, 'Ham Sandwhich', '3.47', none}] + +// products = sql db { +// select from Product where quantity !is none +// }! + +// assert products == [Product{1, 'Ice Cream', '5.99', 17}, Product{3, 'Bagel', '1.25', 45}] + +// products = sql db { +// select from Product where price > '3' && price < '3.50' +// }! + +// assert products == [Product{2, 'Ham Sandwhich', '3.47', none}] + +// products = sql db { +// select from Product where price < '2.000' || price >= '5' +// }! + +// assert products == [Product{1, 'Ice Cream', '5.99', 17}, Product{3, 'Bagel', '1.25', 45}] +// } diff --git a/vsql/page.v b/vsql/page.v index 4de7bfd..e10da57 100644 --- a/vsql/page.v +++ b/vsql/page.v @@ -80,7 +80,7 @@ fn new_reference_object(key []u8, tid int, xid int, blob_peices int, has_fragmen } fn (o PageObject) length() int { - return vsql.page_object_prefix_length + o.key.len + o.value.len + return page_object_prefix_length + o.key.len + o.value.len } // blob_info only applies to blob objects. @@ -113,7 +113,7 @@ fn parse_page_object(data []u8) (int, PageObject) { key_len := buf.read_i16() key := buf.read_u8s(key_len) is_blob_ref := buf.read_bool() - value := buf.read_u8s(total_len - vsql.page_object_prefix_length - key_len) + value := buf.read_u8s(total_len - page_object_prefix_length - key_len) return total_len, PageObject{key, value, is_blob_ref, tid, xid} } @@ -132,17 +132,17 @@ mut: fn new_page(kind u8, page_size int) &Page { return &Page{ kind: kind - used: vsql.page_header_size // includes kind and self - data: []u8{len: page_size - vsql.page_header_size} + used: page_header_size // includes kind and self + data: []u8{len: page_size - page_header_size} } } fn (p Page) is_empty() bool { - return p.used == vsql.page_header_size + return p.used == page_header_size } fn (p Page) page_size() int { - return p.data.len + vsql.page_header_size + return p.data.len + page_header_size } // TODO(elliotchance): This really isn't the most efficient way to do this. Make @@ -307,7 +307,7 @@ fn (p Page) objects() []PageObject { mut objects := []PageObject{} mut n := 0 - for n < p.used - vsql.page_header_size { + for n < p.used - page_header_size { // Be careful to clone the size as the underlying data might get moved // around. m, object := parse_page_object(p.data[n..].clone()) diff --git a/vsql/pager.v b/vsql/pager.v index 3f875ba..1637eda 100644 --- a/vsql/pager.v +++ b/vsql/pager.v @@ -85,7 +85,7 @@ fn new_file_pager(mut file os.File, page_size int, root_page int) !&FilePager { file_len := file.tell() or { return error('unable to get file length: ${err}') } return &FilePager{ - file: file + file: file page_size: page_size root_page: root_page // The first page is reserved for header information. We do not include diff --git a/vsql/planner.v b/vsql/planner.v index e11b125..ffc02f9 100644 --- a/vsql/planner.v +++ b/vsql/planner.v @@ -162,7 +162,7 @@ fn create_select_plan_without_join(body QuerySpecification, from_clause TablePri mut subplan_columns := []Column{} for col in subplan.columns() { subplan_columns << Column{Identifier{ - entity_name: table_name.id() + entity_name: table_name.id() sub_entity_name: col.name.sub_entity_name }, col.typ, col.not_null} } @@ -170,7 +170,7 @@ fn create_select_plan_without_join(body QuerySpecification, from_clause TablePri // NOTE: This has to be assigned to a variable otherwise the value // is lost. This must be a bug in V. table = Table{ - name: table_name + name: table_name columns: subplan_columns } @@ -209,7 +209,7 @@ fn add_group_by_plan(mut plan Plan, group_clause []Identifier, select_exprs []De // expressions contain an aggregate function we need to have an implicit // GROUP BY for the whole set. mut c := Compiler{ - conn: conn + conn: conn params: params tables: tables } @@ -228,7 +228,7 @@ fn add_group_by_plan(mut plan Plan, group_clause []Identifier, select_exprs []De mut order := []SortSpecification{} for col in group_clause { order << SortSpecification{ - expr: ValueExpression(BooleanValueExpression{ + expr: ValueExpression(BooleanValueExpression{ term: BooleanTerm{ factor: BooleanTest{ expr: BooleanPrimary(BooleanPredicand(NonparenthesizedValueExpressionPrimary(col))) @@ -250,9 +250,9 @@ fn add_group_by_plan(mut plan Plan, group_clause []Identifier, select_exprs []De fn create_delete_plan(stmt DeleteStatementSearched, params map[string]Value, mut conn Connection) !Plan { select_stmt := QuerySpecification{ - exprs: AsteriskExpr(true) + exprs: AsteriskExpr(true) table_expression: TableExpression{ - from_clause: TablePrimary{ + from_clause: TablePrimary{ body: stmt.table_name } where_clause: stmt.where @@ -266,9 +266,9 @@ fn create_delete_plan(stmt DeleteStatementSearched, params map[string]Value, mut fn create_update_plan(stmt UpdateStatementSearched, params map[string]Value, mut conn Connection) !Plan { select_stmt := QuerySpecification{ - exprs: AsteriskExpr(true) + exprs: AsteriskExpr(true) table_expression: TableExpression{ - from_clause: TablePrimary{ + from_clause: TablePrimary{ body: stmt.table_name } where_clause: stmt.where @@ -288,7 +288,7 @@ fn create_query_expression_plan(stmt QueryExpression, params map[string]Value, m mut order := []SortSpecification{} for spec in stmt.order { order << SortSpecification{ - expr: spec.expr + expr: spec.expr is_asc: spec.is_asc } } @@ -409,14 +409,12 @@ fn new_expr_operation(mut conn Connection, params map[string]Value, select_list mut column_name := 'COL${i + 1}' if column.as_clause.sub_entity_name != '' { column_name = column.as_clause.sub_entity_name - } else if column.expr is CommonValueExpression { - if column.expr is DatetimePrimary { - if column.expr is ValueExpressionPrimary { - if column.expr is NonparenthesizedValueExpressionPrimary { - if column.expr is Identifier { - e := column.expr - column_name = e.sub_entity_name - } + } else if column.expr is BooleanValueExpression { + e := column.expr.term.factor.expr + if e is BooleanPredicand { + if e is NonparenthesizedValueExpressionPrimary { + if e is Identifier { + column_name = e.sub_entity_name } } } @@ -427,7 +425,7 @@ fn new_expr_operation(mut conn Connection, params map[string]Value, select_list sub_entity_name: column_name } mut c := Compiler{ - conn: conn + conn: conn params: params tables: tables } @@ -452,7 +450,7 @@ fn (o ExprOperation) columns() Columns { fn (mut o ExprOperation) execute(rows []Row) ![]Row { mut c := Compiler{ - conn: o.conn + conn: o.conn params: o.params tables: o.tables } diff --git a/vsql/query_cache.v b/vsql/query_cache.v deleted file mode 100644 index ba77bc3..0000000 --- a/vsql/query_cache.v +++ /dev/null @@ -1,171 +0,0 @@ -// query_cache.v provides tooling to cache previously parsed prepared -// statements. This is becuase parsing a statement is extremely expensive with -// the current Earley implementation and many queries (excluding values) are -// used more than once. -// -// The query cache is made more useful by the fact it can turn any existing -// query into a prepared statement so that cache works in all cases. - -module vsql - -// A QueryCache improves the performance of parsing by caching previously cached -// statements. By default, a new QueryCache is created for each Connection. -// However, you can share a single QueryCache safely amung multiple connections -// for even better performance. See ConnectionOptions. -@[heap] -pub struct QueryCache { -mut: - stmts map[string]Stmt -} - -// Create a new query cache. -pub fn new_query_cache() &QueryCache { - return &QueryCache{} -} - -fn (q QueryCache) prepare(tokens []Token) (string, map[string]Value, []Token) { - // It's only worth caching specific types of queries. - match tokens[0].value { - 'SELECT', 'INSERT', 'UPDATE', 'DELETE' { return q.prepare_stmt(tokens) } - else { return '', map[string]Value{}, tokens } - } -} - -fn (q QueryCache) prepare_stmt(tokens []Token) (string, map[string]Value, []Token) { - mut key := '' - mut i := 0 - mut params := map[string]Value{} - - // TODO(elliotchance): It's not efficient to expand the number of tokens - // like this. Perhaps the parser should just understand a new type of - // placeholder so it can be replaced in place? - mut new_tokens := []Token{cap: tokens.len} - - mut j := 0 - for j < tokens.len { - token := tokens[j] - - // Special handling for named literals. - if j < tokens.len - 1 && token.kind == .keyword - && (token.value == 'TIMESTAMP' || token.value == 'TIME' || token.value == 'DATE') - && tokens[j + 1].kind == .literal_string { - v := match token.value { - 'DATE' { - new_date_value(tokens[j + 1].value) or { panic(err) } - } - 'TIME' { - new_time_value(tokens[j + 1].value) or { panic(err) } - } - 'TIMESTAMP' { - new_timestamp_value(tokens[j + 1].value) or { panic(err) } - } - else { - panic(token.value) - } - } - params['P${i}'] = v - - key += ':P${i} ' - new_tokens << Token{.colon, ':'} - new_tokens << Token{.literal_identifier, 'P${i}'} - i++ - - j += 2 - continue - } - - // Do not replace numbers that appear in types. Such as 'NUMERIC(10, 2)'. - if j < tokens.len - 6 && token.kind == .keyword - && (token.value == 'DECIMAL' || token.value == 'NUMERIC') && tokens[j + 1].value == '(' - && tokens[j + 3].value == ',' { - key += tokens[j].value.to_upper() + ' ' - key += tokens[j + 1].value.to_upper() + ' ' - key += tokens[j + 2].value.to_upper() + ' ' - key += tokens[j + 3].value.to_upper() + ' ' - key += tokens[j + 4].value.to_upper() + ' ' - key += tokens[j + 5].value.to_upper() + ' ' - new_tokens << tokens[j..j + 6] - j += 6 - continue - } - - // Do not replace numbers that appear in types. Such as 'VARCHAR(10)'. - if j < tokens.len - 4 && token.kind == .keyword && (token.value == 'VARCHAR' - || token.value == 'CHAR' || token.value == 'VARYING' - || token.value == 'DECIMAL' || token.value == 'NUMERIC' - || token.value == 'TIMESTAMP' || token.value == 'TIME') && tokens[j + 1].value == '(' { - key += tokens[j].value.to_upper() + ' ' - key += tokens[j + 1].value.to_upper() + ' ' - key += tokens[j + 2].value.to_upper() + ' ' - key += tokens[j + 3].value.to_upper() + ' ' - new_tokens << tokens[j..j + 4] - j += 4 - continue - } - - match token.kind { - .literal_number { - mut numeric_tokens := '' - // Numeric values with a decimal and approximate literals (1e2) are - // actually multiple tokens like [number, '.' number] or - // [number, 'E', number] so we need to be careful to consume all. - for j < tokens.len && (tokens[j].kind == .literal_number - || tokens[j].kind == .period || tokens[j].value == 'E') { - numeric_tokens += tokens[j].value - j++ - } - - // This should never fail as the value is already well formed, but we - // have to satisfy the compiler with an "or". - v := numeric_literal(numeric_tokens) or { panic(numeric_tokens) } - params['P${i}'] = v - - key += ':P${i} ' - new_tokens << Token{.colon, ':'} - new_tokens << Token{.literal_identifier, 'P${i}'} - i++ - continue - } - .literal_string { - key += ':P${i} ' - params['P${i}'] = new_varchar_value(token.value) - new_tokens << Token{.colon, ':'} - new_tokens << Token{.literal_identifier, 'P${i}'} - i++ - j++ - continue - } - else {} - } - - key += token.value.to_upper() + ' ' - new_tokens << token - j++ - } - - return key, params, new_tokens -} - -fn (mut q QueryCache) parse(query string) !(Stmt, map[string]Value, bool) { - mut tokens := tokenize(query) - - // EXPLAIN is super helpful, but not part of the SQL standard so we only - // treat it as a prefix that is trimmed off before parsing. - mut explain := false - if tokens[0].value.to_upper() == 'EXPLAIN' { - explain = true - tokens = tokens[1..].clone() - } - - key, params, new_tokens := q.prepare(tokens) - if key == '' { - stmt := parse(new_tokens)! - return stmt, map[string]Value{}, explain - } - - if key !in q.stmts { - q.stmts[key] = parse(new_tokens)! - } - - return q.stmts[key] or { panic('impossible') }, params, explain -} diff --git a/vsql/result.v b/vsql/result.v index 323db15..460ad08 100644 --- a/vsql/result.v +++ b/vsql/result.v @@ -25,10 +25,10 @@ mut: pub fn new_result(columns Columns, rows []Row, elapsed_parse time.Duration, elapsed_exec time.Duration) Result { return Result{ - columns: columns - rows: rows + columns: columns + rows: rows elapsed_parse: elapsed_parse - elapsed_exec: elapsed_exec + elapsed_exec: elapsed_exec } } diff --git a/vsql/server.v b/vsql/server.v index c1b75a9..691733a 100644 --- a/vsql/server.v +++ b/vsql/server.v @@ -29,14 +29,14 @@ pub fn new_server(options ServerOptions) Server { catalog := &CatalogConnection{ catalog_name: catalog_name - storage: new_storage(btree) - options: default_connection_options() + storage: new_storage(btree) + options: default_connection_options() } return Server{options, &Connection{ query_cache: new_query_cache() - now: default_now - catalogs: { + now: default_now + catalogs: { catalog_name: catalog } }} diff --git a/vsql/sql_test.v b/vsql/sql_test.v index 415ca1e..5ddc85e 100644 --- a/vsql/sql_test.v +++ b/vsql/sql_test.v @@ -148,14 +148,13 @@ fn replace_unicode(s string) string { fn test_all() ! { filter_test, filter_line := get_test_filter() verbose := $env('VERBOSE') - query_cache := new_query_cache() for test in get_tests()! { - run_single_test(test, query_cache, verbose != '', filter_line)! + run_single_test(test, verbose != '', filter_line)! } } @[assert_continues] -fn run_single_test(test SQLTest, query_cache &QueryCache, verbose bool, filter_line int) ! { +fn run_single_test(test SQLTest, verbose bool, filter_line int) ! { if filter_line != 0 && test.line_number != filter_line { if verbose { println('SKIP ${test.file_name}:${test.line_number}\n') @@ -172,17 +171,16 @@ fn run_single_test(test SQLTest, query_cache &QueryCache, verbose bool, filter_l } mut options := default_connection_options() - options.query_cache = query_cache mut db := open_database(':memory:', options)! db.now = fn () (time.Time, i16) { return time.new(time.Time{ - year: 2022 - month: 7 - day: 4 - hour: 14 - minute: 5 - second: 3 + year: 2022 + month: 7 + day: 4 + hour: 14 + minute: 5 + second: 3 nanosecond: 120056000 }), 300 } @@ -276,3 +274,17 @@ fn run_single_test(test SQLTest, query_cache &QueryCache, verbose bool, filter_l assert expected == actual_trim } + +// Make sure any non-keywords (operators, literals, etc) are replaced in error +// messages. +fn test_cleanup_yacc_error() { + mut msg := '' + for tok_name in yy_toknames { + if tok_name.starts_with('OPERATOR_') || tok_name.starts_with('LITERAL_') { + msg += ' ' + tok_name + } + } + result := cleanup_yacc_error(msg) + assert !result.contains('OPERATOR_') + assert !result.contains('LITERAL_') +} diff --git a/vsql/std_10_10_sort_specification_list.v b/vsql/std_10_10_sort_specification_list.v new file mode 100644 index 0000000..3c04867 --- /dev/null +++ b/vsql/std_10_10_sort_specification_list.v @@ -0,0 +1,18 @@ +module vsql + +// ISO/IEC 9075-2:2016(E), 10.10, +// +// Specify a sort order. + +struct SortSpecification { + expr ValueExpression + is_asc bool +} + +fn (e SortSpecification) pstr(params map[string]Value) string { + if e.is_asc { + return '${e.expr.pstr(params)} ASC' + } + + return '${e.expr.pstr(params)} DESC' +} diff --git a/vsql/std_10_10_sort_specification_list.y b/vsql/std_10_10_sort_specification_list.y new file mode 100644 index 0000000..c6cd0f3 --- /dev/null +++ b/vsql/std_10_10_sort_specification_list.y @@ -0,0 +1,22 @@ +%% + +sort_specification_list: + sort_specification { $$.v = [$1.v as SortSpecification] } +| sort_specification_list comma sort_specification { + $$.v = append_list($1.v as []SortSpecification, $3.v as SortSpecification) + } + +sort_specification: + sort_key { $$.v = SortSpecification{$1.v as ValueExpression, true} } +| sort_key ordering_specification { + $$.v = SortSpecification{$1.v as ValueExpression, $2.v as bool} + } + +sort_key: + value_expression { $$.v = $1.v as ValueExpression } + +ordering_specification: + ASC { $$.v = true } +| DESC { $$.v = false } + +%% diff --git a/vsql/std_routine_invocation.v b/vsql/std_10_4_routine_invocation.v similarity index 51% rename from vsql/std_routine_invocation.v rename to vsql/std_10_4_routine_invocation.v index 45fa376..777595a 100644 --- a/vsql/std_routine_invocation.v +++ b/vsql/std_10_4_routine_invocation.v @@ -2,26 +2,7 @@ module vsql // ISO/IEC 9075-2:2016(E), 10.4, // -// # Function -// // Invoke an SQL-invoked routine. -// -// # Format -//~ -//~ /* RoutineInvocation */ ::= -//~ -> routine_invocation -//~ -//~ /* Identifier */ ::= -//~ -> routine_name -//~ -//~ /* []ValueExpression */ ::= -//~ -> sql_argument_list_1 -//~ | -> sql_argument_list_2 -//~ | -//~ -> sql_argument_list_3 -//~ -//~ /* ValueExpression */ ::= -//~ struct RoutineInvocation { function_name string @@ -45,7 +26,7 @@ fn (e RoutineInvocation) compile(mut c Compiler) !CompileResult { if found_func.is_agg { return Identifier{ - custom_id: e.pstr(c.params) + custom_id: e.pstr(c.params) custom_typ: found_func.return_type }.compile(mut c)!.with_agg(true) } @@ -62,7 +43,7 @@ fn (e RoutineInvocation) compile(mut c Compiler) !CompileResult { } return CompileResult{ - run: fn [found_func, func_name, arg_types, compiled_args] (mut conn Connection, data Row, params map[string]Value) !Value { + run: fn [found_func, func_name, arg_types, compiled_args] (mut conn Connection, data Row, params map[string]Value) !Value { mut args := []Value{} mut i := 0 for typ in arg_types { @@ -73,7 +54,7 @@ fn (e RoutineInvocation) compile(mut c Compiler) !CompileResult { return found_func.func(args)! } - typ: found_func.return_type + typ: found_func.return_type contains_agg: found_func.is_agg } } @@ -81,26 +62,3 @@ fn (e RoutineInvocation) compile(mut c Compiler) !CompileResult { fn (e RoutineInvocation) resolve_identifiers(conn &Connection, tables map[string]Table) !RoutineInvocation { return RoutineInvocation{e.function_name, e.args} } - -fn parse_routine_invocation(name Identifier, args []ValueExpression) !RoutineInvocation { - return RoutineInvocation{name.entity_name, args} -} - -fn parse_routine_name(identifier IdentifierChain) !Identifier { - return new_function_identifier(identifier.identifier) -} - -fn parse_sql_argument_list_1() ![]ValueExpression { - return []ValueExpression{} -} - -fn parse_sql_argument_list_2(expr ValueExpression) ![]ValueExpression { - return [expr] -} - -fn parse_sql_argument_list_3(element_list []ValueExpression, element ValueExpression) ![]ValueExpression { - mut new_list := element_list.clone() - new_list << element - - return new_list -} diff --git a/vsql/std_10_4_routine_invocation.y b/vsql/std_10_4_routine_invocation.y new file mode 100644 index 0000000..039ab8f --- /dev/null +++ b/vsql/std_10_4_routine_invocation.y @@ -0,0 +1,23 @@ +%% + +routine_invocation: + routine_name sql_argument_list { + $$.v = RoutineInvocation{($1.v as Identifier).entity_name, $2.v as []ValueExpression} + } + +routine_name: + qualified_identifier { + $$.v = new_function_identifier(($1.v as IdentifierChain).identifier)! + } + +sql_argument_list: + left_paren right_paren { $$.v = []ValueExpression{} } +| left_paren sql_argument right_paren { $$.v = [$2.v as ValueExpression] } +| left_paren sql_argument_list comma sql_argument right_paren { + $$.v = append_list($2.v as []ValueExpression, $4.v as ValueExpression) + } + +sql_argument: + value_expression { $$.v = $1.v as ValueExpression } + +%% diff --git a/vsql/std_aggregate_function.v b/vsql/std_10_9_aggregate_function.v similarity index 74% rename from vsql/std_aggregate_function.v rename to vsql/std_10_9_aggregate_function.v index 9dcff17..1d55028 100644 --- a/vsql/std_aggregate_function.v +++ b/vsql/std_10_9_aggregate_function.v @@ -2,29 +2,7 @@ module vsql // ISO/IEC 9075-2:2016(E), 10.9, // -// # Function -// // Specify a value computed from a collection of rows. -// -// # Format -//~ -//~ /* AggregateFunction */ ::= -//~ COUNT -> count_all -//~ | -//~ -//~ /* AggregateFunction */ ::= -//~ -//~ -> general_set_function -//~ -//~ /* string */ ::= -//~ -//~ -//~ /* string */ ::= -//~ AVG -//~ | MAX -//~ | MIN -//~ | SUM -//~ | COUNT type AggregateFunction = AggregateFunctionCount | RoutineInvocation @@ -39,15 +17,15 @@ fn (e AggregateFunction) compile(mut c Compiler) !CompileResult { match e { AggregateFunctionCount { compiled := Identifier{ - custom_id: 'COUNT(*)' + custom_id: 'COUNT(*)' custom_typ: new_type('INTEGER', 0, 0) }.compile(mut c)! return CompileResult{ - run: fn [compiled] (mut conn Connection, data Row, params map[string]Value) !Value { + run: fn [compiled] (mut conn Connection, data Row, params map[string]Value) !Value { return compiled.run(mut conn, data, params)! } - typ: new_type('INTEGER', 0, 0) + typ: new_type('INTEGER', 0, 0) contains_agg: true } } @@ -59,14 +37,6 @@ fn (e AggregateFunction) compile(mut c Compiler) !CompileResult { struct AggregateFunctionCount {} -fn parse_count_all(asterisk string) !AggregateFunction { - return AggregateFunctionCount{} -} - -fn parse_general_set_function(name string, expr ValueExpression) !AggregateFunction { - return RoutineInvocation{name, [expr]} -} - // COUNT(ANY) INTEGER fn func_count(values []Value) !Value { mut count := 0 diff --git a/vsql/std_10_9_aggregate_function.y b/vsql/std_10_9_aggregate_function.y new file mode 100644 index 0000000..f920c42 --- /dev/null +++ b/vsql/std_10_9_aggregate_function.y @@ -0,0 +1,26 @@ +%% + +// was: COUNT left_paren asterisk right_paren +aggregate_function: + COUNT OPERATOR_LEFT_PAREN_ASTERISK right_paren { + $$.v = AggregateFunction(AggregateFunctionCount{}) + } +| general_set_function { $$.v = $1.v as AggregateFunction } + +general_set_function: + set_function_type left_paren value_expression right_paren { + $$.v = AggregateFunction(RoutineInvocation{ + $1.v as string, [$3.v as ValueExpression]}) + } + +set_function_type: + computational_operation { $$.v = $1.v as string } + +computational_operation: + AVG { $$.v = $1.v as string } +| MAX { $$.v = $1.v as string } +| MIN { $$.v = $1.v as string } +| SUM { $$.v = $1.v as string } +| COUNT { $$.v = $1.v as string } + +%% diff --git a/vsql/std_schema_definition.v b/vsql/std_11_1_schema_definition.v similarity index 73% rename from vsql/std_schema_definition.v rename to vsql/std_11_1_schema_definition.v index c7e6f39..0c9c13a 100644 --- a/vsql/std_schema_definition.v +++ b/vsql/std_11_1_schema_definition.v @@ -4,26 +4,12 @@ import time // ISO/IEC 9075-2:2016(E), 11.1, // -// # Function -// // Define a schema. -// -// # Format -//~ -//~ /* Stmt */ ::= -//~ CREATE SCHEMA -> schema_definition -//~ -//~ /* Identifier */ ::= -//~ struct SchemaDefinition { schema_name Identifier } -fn parse_schema_definition(schema_name Identifier) !Stmt { - return SchemaDefinition{schema_name} -} - fn (stmt SchemaDefinition) execute(mut conn Connection, params map[string]Value, elapsed_parse time.Duration) !Result { t := start_timer() diff --git a/vsql/std_11_1_schema_definition.y b/vsql/std_11_1_schema_definition.y new file mode 100644 index 0000000..5889c42 --- /dev/null +++ b/vsql/std_11_1_schema_definition.y @@ -0,0 +1,11 @@ +%% + +schema_definition: + CREATE SCHEMA schema_name_clause { + $$.v = Stmt(SchemaDefinition{$3.v as Identifier}) + } + +schema_name_clause: + schema_name { $$.v = $1.v as Identifier } + +%% diff --git a/vsql/std_drop_schema_statement.v b/vsql/std_11_2_drop_schema_statement.v similarity index 79% rename from vsql/std_drop_schema_statement.v rename to vsql/std_11_2_drop_schema_statement.v index edb8691..e49460e 100644 --- a/vsql/std_drop_schema_statement.v +++ b/vsql/std_11_2_drop_schema_statement.v @@ -4,28 +4,13 @@ import time // ISO/IEC 9075-2:2016(E), 11.2, // -// # Function -// // Destroy a schema. -// -// # Format -//~ -//~ /* Stmt */ ::= -//~ DROP SCHEMA -> drop_schema_statement -//~ -//~ /* string */ ::= -//~ CASCADE -//~ | RESTRICT struct DropSchemaStatement { schema_name Identifier behavior string // CASCADE or RESTRICT } -fn parse_drop_schema_statement(schema_name Identifier, behavior string) !Stmt { - return DropSchemaStatement{schema_name, behavior} -} - fn (stmt DropSchemaStatement) execute(mut conn Connection, params map[string]Value, elapsed_parse time.Duration) !Result { t := start_timer() diff --git a/vsql/std_11_2_drop_schema_statement.y b/vsql/std_11_2_drop_schema_statement.y new file mode 100644 index 0000000..b1a609b --- /dev/null +++ b/vsql/std_11_2_drop_schema_statement.y @@ -0,0 +1,12 @@ +%% + +drop_schema_statement: + DROP SCHEMA schema_name drop_behavior { + $$.v = Stmt(DropSchemaStatement{$3.v as Identifier, $4.v as string}) + } + +drop_behavior: + CASCADE { $$.v = $1.v as string } +| RESTRICT { $$.v = $1.v as string } + +%% diff --git a/vsql/std_drop_table_statement.v b/vsql/std_11_31_drop_table_statement.v similarity index 79% rename from vsql/std_drop_table_statement.v rename to vsql/std_11_31_drop_table_statement.v index 57501b5..1795e7a 100644 --- a/vsql/std_drop_table_statement.v +++ b/vsql/std_11_31_drop_table_statement.v @@ -4,23 +4,12 @@ import time // ISO/IEC 9075-2:2016(E), 11.31, // -// # Function -// // Destroy a table. -// -// # Format -//~ -//~ /* Stmt */ ::= -//~ DROP TABLE -> drop_table_statement struct DropTableStatement { table_name Identifier } -fn parse_drop_table_statement(table_name Identifier) !Stmt { - return DropTableStatement{table_name} -} - fn (stmt DropTableStatement) execute(mut conn Connection, params map[string]Value, elapsed_parse time.Duration) !Result { t := start_timer() diff --git a/vsql/std_11_31_drop_table_statement.y b/vsql/std_11_31_drop_table_statement.y new file mode 100644 index 0000000..968768e --- /dev/null +++ b/vsql/std_11_31_drop_table_statement.y @@ -0,0 +1,6 @@ +%% + +drop_table_statement: + DROP TABLE table_name { $$.v = Stmt(DropTableStatement{$3.v as Identifier}) } + +%% diff --git a/vsql/std_table_definition.v b/vsql/std_11_3_table_definition.v similarity index 64% rename from vsql/std_table_definition.v rename to vsql/std_11_3_table_definition.v index ee0e6cd..bafb12d 100644 --- a/vsql/std_table_definition.v +++ b/vsql/std_11_3_table_definition.v @@ -4,33 +4,8 @@ import time // ISO/IEC 9075-2:2016(E), 11.3,
    // -// # Function -// // Define a persistent base table, a created local temporary table, or a global // temporary table. -// -// # Format -//~ -//~
    /* TableDefinition */ ::= -//~ CREATE TABLE
    -> table_definition -//~ -//~
    /* []TableElement */ ::= -//~
    -//~ -//~
    /* []TableElement */ ::= -//~ -//~
    -//~ -> table_element_list -//~ -//~
    /* TableElement */ ::= -//~ -//~ |
    -// -// These are non-standard, just to simplify standard rules: -//~ -//~
    /* []TableElement */ ::= -//~
    -> table_elements_1 -//~ |
    -> table_elements_2 type TableElement = Column | UniqueConstraintDefinition @@ -50,24 +25,6 @@ fn (s TableDefinition) columns() Columns { return columns } -fn parse_table_definition(table_name Identifier, table_contents_source []TableElement) !Stmt { - return TableDefinition{table_name, table_contents_source} -} - -fn parse_table_element_list(table_elements []TableElement) ![]TableElement { - return table_elements -} - -fn parse_table_elements_1(table_element TableElement) ![]TableElement { - return [table_element] -} - -fn parse_table_elements_2(table_elements []TableElement, table_element TableElement) ![]TableElement { - mut new_table_elements := table_elements.clone() - new_table_elements << table_element - return new_table_elements -} - // TODO(elliotchance): A table is allowed to have zero columns. fn (stmt TableDefinition) execute(mut conn Connection, params map[string]Value, elapsed_parse time.Duration) !Result { @@ -95,9 +52,9 @@ fn (stmt TableDefinition) execute(mut conn Connection, params map[string]Value, } columns << Column{Identifier{ - catalog_name: table_name.catalog_name - schema_name: table_name.schema_name - entity_name: table_name.entity_name + catalog_name: table_name.catalog_name + schema_name: table_name.schema_name + entity_name: table_name.entity_name sub_entity_name: table_element.name.sub_entity_name }, table_element.typ, table_element.not_null} } diff --git a/vsql/std_11_3_table_definition.y b/vsql/std_11_3_table_definition.y new file mode 100644 index 0000000..d6b3d23 --- /dev/null +++ b/vsql/std_11_3_table_definition.y @@ -0,0 +1,24 @@ +%% + +table_definition: + CREATE TABLE table_name table_contents_source { + $$.v = Stmt(TableDefinition{$3.v as Identifier, $4.v as []TableElement}) + } + +table_contents_source: + table_element_list { $$.v = $1.v as []TableElement } + +table_element_list: + left_paren table_elements right_paren { $$.v = $2.v as []TableElement } + +table_element: + column_definition { $$.v = $1.v as TableElement } +| table_constraint_definition { $$.v = $1.v as TableElement } + +table_elements: + table_element { $$.v = [$1.v as TableElement] } +| table_elements comma table_element { + $$.v = append_list($1.v as []TableElement, $3.v as TableElement) + } + +%% diff --git a/vsql/std_11_4_column_definition.y b/vsql/std_11_4_column_definition.y new file mode 100644 index 0000000..9749aba --- /dev/null +++ b/vsql/std_11_4_column_definition.y @@ -0,0 +1,24 @@ +%% + +// ISO/IEC 9075-2:2016(E), 11.4, +// +// Define a column of a base table. + +column_definition: + column_name data_type_or_domain_name { + $$.v = TableElement(Column{$1.v as Identifier, $2.v as Type, false}) + } +| column_name data_type_or_domain_name column_constraint_definition { + $$.v = TableElement(Column{$1.v as Identifier, $2.v as Type, $3.v as bool}) + } + +data_type_or_domain_name: + data_type { $$.v = $1.v as Type } + +column_constraint_definition: + column_constraint { $$.v = $1.v as bool } + +column_constraint: + NOT NULL { $$.v = true } + +%% diff --git a/vsql/std_11_6_table_constraint_definition.y b/vsql/std_11_6_table_constraint_definition.y new file mode 100644 index 0000000..1148dd1 --- /dev/null +++ b/vsql/std_11_6_table_constraint_definition.y @@ -0,0 +1,15 @@ +%% + +// ISO/IEC 9075-2:2016(E), 11.6,
    +// +// Specify an integrity constraint. + +table_constraint_definition: + table_constraint { $$.v = $1.v as TableElement } + +table_constraint: + unique_constraint_definition { + $$.v = TableElement($1.v as UniqueConstraintDefinition) + } + +%% diff --git a/vsql/std_11_72_sequence_generator_definition.v b/vsql/std_11_72_sequence_generator_definition.v new file mode 100644 index 0000000..1e5aa90 --- /dev/null +++ b/vsql/std_11_72_sequence_generator_definition.v @@ -0,0 +1,133 @@ +module vsql + +import time + +// ISO/IEC 9075-2:2016(E), 11.72, +// +// Define an external sequence generator. + +type SequenceGeneratorOption = SequenceGeneratorCycleOption + | SequenceGeneratorIncrementByOption + | SequenceGeneratorMaxvalueOption + | SequenceGeneratorMinvalueOption + | SequenceGeneratorRestartOption + | SequenceGeneratorStartWithOption + +struct SequenceGeneratorStartWithOption { + start_value Value +} + +struct SequenceGeneratorRestartOption { + restart_value ?Value +} + +struct SequenceGeneratorIncrementByOption { + increment_by Value +} + +struct SequenceGeneratorMinvalueOption { + min_value ?Value // not set = NO MINVALUE +} + +struct SequenceGeneratorMaxvalueOption { + max_value ?Value // not set = NO MAXVALUE +} + +struct SequenceGeneratorCycleOption { + cycle bool +} + +struct SequenceGeneratorDefinition { + name Identifier + options []SequenceGeneratorOption +} + +fn (stmt SequenceGeneratorDefinition) execute(mut conn Connection, params map[string]Value, elapsed_parse time.Duration) !Result { + t := start_timer() + + conn.open_write_connection()! + defer { + conn.release_write_connection() + } + + mut c := Compiler{ + conn: conn + params: params + } + mut catalog := conn.catalog() + mut sequence_name := conn.resolve_schema_identifier(stmt.name)! + mut increment_by := i64(1) + mut has_start_value := false + mut start_value := i64(1) + mut has_min_value := false + mut min_value := i64(0) + mut has_max_value := false + mut max_value := i64(0) + mut cycle := false + for option in stmt.options { + match option { + SequenceGeneratorStartWithOption { + start_value = (option.start_value.compile(mut c)!.run(mut conn, Row{}, + map[string]Value{})!).as_int() + has_start_value = true + } + SequenceGeneratorRestartOption { + // Not possible. + } + SequenceGeneratorIncrementByOption { + increment_by = (option.increment_by.compile(mut c)!.run(mut conn, Row{}, + map[string]Value{})!).as_int() + } + SequenceGeneratorMinvalueOption { + if v := option.min_value { + min_value = (v.compile(mut c)!.run(mut conn, Row{}, map[string]Value{})!).as_int() + has_min_value = true + } + } + SequenceGeneratorMaxvalueOption { + if v := option.max_value { + max_value = (v.compile(mut c)!.run(mut conn, Row{}, map[string]Value{})!).as_int() + has_max_value = true + } + } + SequenceGeneratorCycleOption { + cycle = option.cycle + } + } + } + + is_ascending := increment_by >= 0 + current_value := match true { + has_start_value { + start_value - increment_by + } + is_ascending && has_min_value { + min_value - increment_by + } + !is_ascending && has_max_value { + max_value - increment_by + } + else { + 1 - increment_by + } + } + + sequence := Sequence{ + name: sequence_name + current_value: current_value + increment_by: increment_by + cycle: cycle + has_min_value: has_min_value + min_value: min_value + has_max_value: has_max_value + max_value: max_value + } + + catalog.storage.create_sequence(sequence)! + + return new_result_msg('CREATE SEQUENCE 1', elapsed_parse, t.elapsed()) +} + +fn (stmt SequenceGeneratorDefinition) explain(mut conn Connection, params map[string]Value, elapsed_parse time.Duration) !Result { + return sqlstate_42601('Cannot EXPLAIN CREATE SEQUENCE') +} diff --git a/vsql/std_11_72_sequence_generator_definition.y b/vsql/std_11_72_sequence_generator_definition.y new file mode 100644 index 0000000..e95d87d --- /dev/null +++ b/vsql/std_11_72_sequence_generator_definition.y @@ -0,0 +1,98 @@ +%% + +sequence_generator_definition: + CREATE SEQUENCE sequence_generator_name { + $$.v = SequenceGeneratorDefinition{ + name: $3.v as Identifier + } + } +| CREATE SEQUENCE sequence_generator_name sequence_generator_options { + $$.v = SequenceGeneratorDefinition{ + name: $3.v as Identifier + options: $4.v as []SequenceGeneratorOption + } + } + +sequence_generator_options: + sequence_generator_option { $$.v = $1.v as []SequenceGeneratorOption } +| sequence_generator_options sequence_generator_option { + $$.v = $1.v as []SequenceGeneratorOption + } + +sequence_generator_option: + common_sequence_generator_options { $$.v = $1.v as []SequenceGeneratorOption } + +common_sequence_generator_options: + common_sequence_generator_option { $$.v = [$1.v as SequenceGeneratorOption] } +| common_sequence_generator_options common_sequence_generator_option { + $$.v = append_list($1.v as []SequenceGeneratorOption, + $2.v as SequenceGeneratorOption) + } + +common_sequence_generator_option: + sequence_generator_start_with_option { + $$.v = SequenceGeneratorOption($1.v as SequenceGeneratorStartWithOption) + } +| basic_sequence_generator_option { $$.v = $1.v as SequenceGeneratorOption } + +basic_sequence_generator_option: + sequence_generator_increment_by_option { + $$.v = SequenceGeneratorOption($1.v as SequenceGeneratorIncrementByOption) + } +| sequence_generator_maxvalue_option { + $$.v = SequenceGeneratorOption($1.v as SequenceGeneratorMaxvalueOption) + } +| sequence_generator_minvalue_option { + $$.v = SequenceGeneratorOption($1.v as SequenceGeneratorMinvalueOption) + } +| sequence_generator_cycle_option { + $$.v = SequenceGeneratorOption(SequenceGeneratorCycleOption{$1.v as bool}) + } + +sequence_generator_start_with_option: + START WITH sequence_generator_start_value { + $$.v = SequenceGeneratorStartWithOption{ + start_value: $3.v as Value + } + } + +sequence_generator_start_value: + signed_numeric_literal { $$.v = $1.v as Value } + +sequence_generator_increment_by_option: + INCREMENT BY sequence_generator_increment { + $$.v = SequenceGeneratorIncrementByOption{ + increment_by: $3.v as Value + } + } + +sequence_generator_increment: + signed_numeric_literal { $$.v = $1.v as Value } + +sequence_generator_maxvalue_option: + MAXVALUE sequence_generator_max_value { + $$.v = SequenceGeneratorMaxvalueOption{ + max_value: $2.v as Value + } + } +| NO MAXVALUE { $$.v = SequenceGeneratorMaxvalueOption{} } + +sequence_generator_max_value: + signed_numeric_literal { $$.v = $1.v as Value } + +sequence_generator_minvalue_option: + MINVALUE sequence_generator_min_value { + $$.v = SequenceGeneratorMinvalueOption{ + min_value: $2.v as Value + } + } +| NO MINVALUE { $$.v = SequenceGeneratorMinvalueOption{} } + +sequence_generator_min_value: + signed_numeric_literal { $$.v = $1.v as Value } + +sequence_generator_cycle_option: + CYCLE { $$.v = true } +| NO CYCLE { $$.v = false } + +%% diff --git a/vsql/std_alter_sequence_generator_statement.v b/vsql/std_11_73_alter_sequence_generator_statement.v similarity index 74% rename from vsql/std_alter_sequence_generator_statement.v rename to vsql/std_11_73_alter_sequence_generator_statement.v index a63316d..26525c6 100644 --- a/vsql/std_alter_sequence_generator_statement.v +++ b/vsql/std_11_73_alter_sequence_generator_statement.v @@ -4,42 +4,18 @@ import time // ISO/IEC 9075-2:2016(E), 11.73, // -// # Function -// // Change the definition of an external sequence generator. -// -// # Format -//~ -//~ /* AlterSequenceGeneratorStatement */ ::= -//~ ALTER SEQUENCE -//~ -//~ -> alter_sequence_generator_statement -//~ -//~ /* []SequenceGeneratorOption */ ::= -//~ -> sequence_generator_options_1 -//~ | -//~ -> sequence_generator_options_2 -//~ -//~ /* SequenceGeneratorOption */ ::= -//~ -> SequenceGeneratorOption -//~ | -//~ -//~ /* SequenceGeneratorRestartOption */ ::= -//~ RESTART -> sequence_generator_restart_option_1 -//~ | RESTART WITH -//~ -> sequence_generator_restart_option_2 -//~ -//~ /* Value */ ::= -//~ struct AlterSequenceGeneratorStatement { name Identifier options []SequenceGeneratorOption } +<<<<<<< HEAD:vsql/std_11_73_alter_sequence_generator_statement.v +======= fn parse_alter_sequence_generator_statement(generator_name Identifier, options []SequenceGeneratorOption) !AlterSequenceGeneratorStatement { return AlterSequenceGeneratorStatement{ - name: generator_name + name: generator_name options: options } } @@ -66,6 +42,7 @@ fn parse_sequence_generator_restart_option_2(restart_value Value) !SequenceGener } } +>>>>>>> a082336 (run make fmt):vsql/std_alter_sequence_generator_statement.v fn (stmt AlterSequenceGeneratorStatement) execute(mut conn Connection, params map[string]Value, elapsed_parse time.Duration) !Result { t := start_timer() @@ -79,7 +56,7 @@ fn (stmt AlterSequenceGeneratorStatement) execute(mut conn Connection, params ma old_sequence := catalog.storage.sequence(name)! mut sequence := old_sequence.copy() mut c := Compiler{ - conn: conn + conn: conn params: params } diff --git a/vsql/std_11_73_alter_sequence_generator_statement.y b/vsql/std_11_73_alter_sequence_generator_statement.y new file mode 100644 index 0000000..59c4834 --- /dev/null +++ b/vsql/std_11_73_alter_sequence_generator_statement.y @@ -0,0 +1,35 @@ +%% + +alter_sequence_generator_statement: + ALTER SEQUENCE sequence_generator_name alter_sequence_generator_options { + $$.v = AlterSequenceGeneratorStatement{ + name: $3.v as Identifier + options: $4.v as []SequenceGeneratorOption + } + } + +alter_sequence_generator_options: + alter_sequence_generator_option { $$.v = [$1.v as SequenceGeneratorOption] } +| alter_sequence_generator_options alter_sequence_generator_option { + $$.v = append_list($1.v as []SequenceGeneratorOption, + $2.v as SequenceGeneratorOption) + } + +alter_sequence_generator_option: + alter_sequence_generator_restart_option { + $$.v = SequenceGeneratorOption($1.v as SequenceGeneratorRestartOption) + } +| basic_sequence_generator_option { $$.v = $1.v as SequenceGeneratorOption } + +alter_sequence_generator_restart_option: + RESTART { $$.v = SequenceGeneratorRestartOption{} } +| RESTART WITH sequence_generator_restart_value { + $$.v = SequenceGeneratorRestartOption{ + restart_value: $3.v as Value + } + } + +sequence_generator_restart_value: + signed_numeric_literal { $$.v = $1.v as Value } + +%% diff --git a/vsql/std_drop_sequence_generator_statement.v b/vsql/std_11_74_drop_sequence_generator_statement.v similarity index 73% rename from vsql/std_drop_sequence_generator_statement.v rename to vsql/std_11_74_drop_sequence_generator_statement.v index 5f21868..447a963 100644 --- a/vsql/std_drop_sequence_generator_statement.v +++ b/vsql/std_11_74_drop_sequence_generator_statement.v @@ -4,24 +4,12 @@ import time // ISO/IEC 9075-2:2016(E), 11.74, // -// # Function -// // Destroy an external sequence generator. -// -// # Format -//~ -//~ /* Stmt */ ::= -//~ DROP SEQUENCE -//~ -> drop_sequence_generator_statement struct DropSequenceGeneratorStatement { sequence_name Identifier } -fn parse_drop_sequence_generator_statement(sequence_name Identifier) !Stmt { - return DropSequenceGeneratorStatement{sequence_name} -} - fn (stmt DropSequenceGeneratorStatement) execute(mut conn Connection, params map[string]Value, elapsed_parse time.Duration) !Result { t := start_timer() diff --git a/vsql/std_11_74_drop_sequence_generator_statement.y b/vsql/std_11_74_drop_sequence_generator_statement.y new file mode 100644 index 0000000..c91d27a --- /dev/null +++ b/vsql/std_11_74_drop_sequence_generator_statement.y @@ -0,0 +1,8 @@ +%% + +drop_sequence_generator_statement: + DROP SEQUENCE sequence_generator_name { + $$.v = Stmt(DropSequenceGeneratorStatement{$3.v as Identifier}) + } + +%% diff --git a/vsql/std_11_7_unique_constraint_definition.v b/vsql/std_11_7_unique_constraint_definition.v new file mode 100644 index 0000000..ec82574 --- /dev/null +++ b/vsql/std_11_7_unique_constraint_definition.v @@ -0,0 +1,9 @@ +module vsql + +// ISO/IEC 9075-2:2016(E), 11.7, +// +// Specify a uniqueness constraint for a table. + +struct UniqueConstraintDefinition { + columns []Identifier +} diff --git a/vsql/std_11_7_unique_constraint_definition.y b/vsql/std_11_7_unique_constraint_definition.y new file mode 100644 index 0000000..378114b --- /dev/null +++ b/vsql/std_11_7_unique_constraint_definition.y @@ -0,0 +1,14 @@ +%% + +unique_constraint_definition: + unique_specification left_paren unique_column_list right_paren { + $$.v = UniqueConstraintDefinition{$3.v as []Identifier} + } + +unique_specification: + PRIMARY KEY + +unique_column_list: + column_name_list { $$.v = $1.v as []Identifier } + +%% diff --git a/vsql/std_13_4_sql_procedure_statement.y b/vsql/std_13_4_sql_procedure_statement.y new file mode 100644 index 0000000..703c63d --- /dev/null +++ b/vsql/std_13_4_sql_procedure_statement.y @@ -0,0 +1,31 @@ +%% + +// ISO/IEC 9075-2:2016(E), 13.4, +// +// Define all of the SQL-statements that are s. + +sql_schema_statement: + sql_schema_definition_statement { $$.v = $1.v as Stmt } +| sql_schema_manipulation_statement { $$.v = $1.v as Stmt } + +sql_schema_definition_statement: + schema_definition { $$.v = $1.v as Stmt } +| table_definition { $$.v = $1.v as Stmt } +| sequence_generator_definition { $$.v = Stmt($1.v as SequenceGeneratorDefinition) } + +sql_schema_manipulation_statement: + drop_schema_statement { $$.v = $1.v as Stmt } +| drop_table_statement { $$.v = $1.v as Stmt } +| alter_sequence_generator_statement { $$.v = Stmt($1.v as AlterSequenceGeneratorStatement) } +| drop_sequence_generator_statement { $$.v = $1.v as Stmt } + +sql_transaction_statement: + start_transaction_statement { $$.v = $1.v as Stmt } +| commit_statement { $$.v = $1.v as Stmt } +| rollback_statement { $$.v = $1.v as Stmt } + +sql_session_statement: + set_schema_statement { $$.v = Stmt($1.v as SetSchemaStatement) } +| set_catalog_statement { $$.v = $1.v as Stmt } + +%% diff --git a/vsql/std_insert_statement.v b/vsql/std_14_11_insert_statement.v similarity index 81% rename from vsql/std_insert_statement.v rename to vsql/std_14_11_insert_statement.v index a91163a..f49d7dc 100644 --- a/vsql/std_insert_statement.v +++ b/vsql/std_14_11_insert_statement.v @@ -4,29 +4,7 @@ import time // ISO/IEC 9075-2:2016(E), 14.11, // -// # Function -// // Create new rows in a table. -// -// # Format -//~ -//~ /* Stmt */ ::= -//~ INSERT INTO -//~ -//~ -> insert_statement -//~ -//~ /* Identifier */ ::= -//~
    -//~ -//~ /* InsertStatement */ ::= -//~ -//~ -//~ /* InsertStatement */ ::= -//~ -//~ -> from_constructor -//~ -//~ /* []Identifier */ ::= -//~ struct InsertStatement { table_name Identifier @@ -41,7 +19,7 @@ fn parse_insert_statement(insertion_target Identifier, stmt InsertStatement) !St fn parse_from_constructor(columns []Identifier, values []ContextuallyTypedRowValueConstructor) !InsertStatement { return InsertStatement{ columns: columns - values: values + values: values } } @@ -96,8 +74,8 @@ fn (stmt InsertStatement) execute(mut conn Connection, params map[string]Value, table_column := table.column(column_name)! mut c := Compiler{ - conn: conn - params: params + conn: conn + params: params null_type: table_column.typ } raw_value := values[i].compile(mut c)!.run(mut conn, Row{}, params)! diff --git a/vsql/std_14_11_insert_statement.y b/vsql/std_14_11_insert_statement.y new file mode 100644 index 0000000..39ada83 --- /dev/null +++ b/vsql/std_14_11_insert_statement.y @@ -0,0 +1,27 @@ +%% + +insert_statement: + INSERT INTO insertion_target insert_columns_and_source { + stmt := $4.v as InsertStatement + $$.v = Stmt(InsertStatement{$3.v as Identifier, stmt.columns, stmt.values}) + } + +insertion_target: + table_name { $$.v = $1.v as Identifier } + +insert_columns_and_source: + from_constructor { $$.v = $1.v as InsertStatement } + +from_constructor: + left_paren insert_column_list right_paren + contextually_typed_table_value_constructor { + $$.v = InsertStatement{ + columns: $2.v as []Identifier + values: $4.v as []ContextuallyTypedRowValueConstructor + } + } + +insert_column_list: + column_name_list { $$.v = $1.v as []Identifier } + +%% diff --git a/vsql/std_update_statement_searched.v b/vsql/std_14_14_update_statement_searched.v similarity index 85% rename from vsql/std_update_statement_searched.v rename to vsql/std_14_14_update_statement_searched.v index e8a0434..64900ca 100644 --- a/vsql/std_update_statement_searched.v +++ b/vsql/std_14_14_update_statement_searched.v @@ -4,18 +4,7 @@ import time // ISO/IEC 9075-2:2016(E), 14.14, // -// # Function -// // Update rows of a table. -// -// # Format -//~ -//~ /* Stmt */ ::= -//~ UPDATE -//~ SET -> update_statement_searched_1 -//~ | UPDATE -//~ SET -//~ WHERE -> update_statement_searched_2 struct UpdateStatementSearched { table_name Identifier @@ -23,14 +12,6 @@ struct UpdateStatementSearched { where ?BooleanValueExpression } -fn parse_update_statement_searched_1(target_table Identifier, set_clause_list map[string]UpdateSource) !Stmt { - return UpdateStatementSearched{target_table, set_clause_list, none} -} - -fn parse_update_statement_searched_2(target_table Identifier, set_clause_list map[string]UpdateSource, where BooleanValueExpression) !Stmt { - return UpdateStatementSearched{target_table, set_clause_list, where} -} - // UPDATE under MVCC works by actually executing a DELETE and an INSERT on the // record to be updated. There are two important caveats for this: // @@ -52,7 +33,7 @@ fn (stmt UpdateStatementSearched) execute(mut conn Connection, params map[string } mut c := Compiler{ - conn: conn + conn: conn params: params } mut catalog := conn.catalog() @@ -77,8 +58,8 @@ fn (stmt UpdateStatementSearched) execute(mut conn Connection, params map[string table_column := table.column(column_name)! c.context = Identifier{ catalog_name: table_column.name.catalog_name - schema_name: table_column.name.schema_name - entity_name: table.name.entity_name + schema_name: table_column.name.schema_name + entity_name: table.name.entity_name } raw_value := match v { diff --git a/vsql/std_14_14_update_statement_searched.y b/vsql/std_14_14_update_statement_searched.y new file mode 100644 index 0000000..fe75dc0 --- /dev/null +++ b/vsql/std_14_14_update_statement_searched.y @@ -0,0 +1,13 @@ +%% + +update_statement_searched: + UPDATE target_table SET set_clause_list { + $$.v = Stmt(UpdateStatementSearched{$2.v as Identifier, + $4.v as map[string]UpdateSource, none}) + } +| UPDATE target_table SET set_clause_list WHERE search_condition { + $$.v = Stmt(UpdateStatementSearched{$2.v as Identifier, + $4.v as map[string]UpdateSource, $6.v as BooleanValueExpression}) + } + +%% diff --git a/vsql/std_14_15_set_clause_list.v b/vsql/std_14_15_set_clause_list.v new file mode 100644 index 0000000..8f6c799 --- /dev/null +++ b/vsql/std_14_15_set_clause_list.v @@ -0,0 +1,23 @@ +module vsql + +// ISO/IEC 9075-2:2016(E), 14.15, +// +// Specify a list of updates. + +type UpdateSource = NullSpecification | ValueExpression + +fn (e UpdateSource) pstr(params map[string]Value) string { + return match e { + ValueExpression, NullSpecification { + e.pstr(params) + } + } +} + +fn (e UpdateSource) compile(mut c Compiler) !CompileResult { + match e { + ValueExpression, NullSpecification { + return e.compile(mut c)! + } + } +} diff --git a/vsql/std_14_15_set_clause_list.y b/vsql/std_14_15_set_clause_list.y new file mode 100644 index 0000000..d707413 --- /dev/null +++ b/vsql/std_14_15_set_clause_list.y @@ -0,0 +1,32 @@ +%% + +set_clause_list: + set_clause { $$.v = $1.v as map[string]UpdateSource } +| set_clause_list comma set_clause { + $$.v = merge_maps($1.v as map[string]UpdateSource, + $3.v as map[string]UpdateSource) + } + +set_clause: + set_target equals_operator update_source { + $$.v = { + ($1.v as Identifier).str(): $3.v as UpdateSource + } + } + +set_target: + update_target { $$.v = $1.v as Identifier } + +update_target: + object_column { $$.v = $1.v as Identifier } + +update_source: + value_expression { $$.v = UpdateSource($1.v as ValueExpression) } +| contextually_typed_value_specification { + $$.v = UpdateSource($1.v as NullSpecification) + } + +object_column: + column_name { $$.v = $1.v as Identifier } + +%% diff --git a/vsql/std_14_3_cursor_specification.y b/vsql/std_14_3_cursor_specification.y new file mode 100644 index 0000000..7b805d7 --- /dev/null +++ b/vsql/std_14_3_cursor_specification.y @@ -0,0 +1,10 @@ +%% + +// ISO/IEC 9075-2:2016(E), 14.3, +// +// Define a result set. + +cursor_specification: + query_expression { $$.v = Stmt($1.v as QueryExpression) } + +%% diff --git a/vsql/std_14_8_delete_statement_positioned.y b/vsql/std_14_8_delete_statement_positioned.y new file mode 100644 index 0000000..78b61d4 --- /dev/null +++ b/vsql/std_14_8_delete_statement_positioned.y @@ -0,0 +1,10 @@ +%% + +// ISO/IEC 9075-2:2016(E), 14.8, +// +// Delete a row of a table. + +target_table: + table_name { $$.v = $1.v as Identifier } + +%% diff --git a/vsql/std_delete_statement_searched.v b/vsql/std_14_9_delete_statement_searched.v similarity index 64% rename from vsql/std_delete_statement_searched.v rename to vsql/std_14_9_delete_statement_searched.v index 71a07f2..3213737 100644 --- a/vsql/std_delete_statement_searched.v +++ b/vsql/std_14_9_delete_statement_searched.v @@ -4,30 +4,13 @@ import time // ISO/IEC 9075-2:2016(E), 14.9, // -// # Function -// // Delete rows of a table. -// -// # Format -//~ -//~ /* Stmt */ ::= -//~ DELETE FROM -> delete_statement -//~ | DELETE FROM -//~ WHERE -> delete_statement_where struct DeleteStatementSearched { table_name Identifier where ?BooleanValueExpression } -fn parse_delete_statement(table_name Identifier) !Stmt { - return DeleteStatementSearched{table_name, none} -} - -fn parse_delete_statement_where(table_name Identifier, where BooleanValueExpression) !Stmt { - return DeleteStatementSearched{table_name, where} -} - fn (stmt DeleteStatementSearched) explain(mut conn Connection, params map[string]Value, elapsed_parse time.Duration) !Result { conn.open_write_connection()! defer { @@ -53,7 +36,9 @@ fn (stmt DeleteStatementSearched) execute(mut conn Connection, params map[string mut rows := plan.execute([]Row{})! for mut row in rows { - catalog.storage.delete_row(table_name.storage_id(), mut row)! + // for_storage() here is important because it will strip the qualified + // identifiers down to just their names used in storage. + catalog.storage.delete_row(table_name.storage_id(), mut row.for_storage())! } return new_result_msg('DELETE ${rows.len}', elapsed_parse, t.elapsed()) diff --git a/vsql/std_14_9_delete_statement_searched.y b/vsql/std_14_9_delete_statement_searched.y new file mode 100644 index 0000000..e5b6db0 --- /dev/null +++ b/vsql/std_14_9_delete_statement_searched.y @@ -0,0 +1,14 @@ +%% + +delete_statement_searched: + DELETE FROM target_table { + $$.v = Stmt(DeleteStatementSearched{$3.v as Identifier, none}) + } +| DELETE FROM target_table WHERE search_condition { + $$.v = Stmt(DeleteStatementSearched{ + $3.v as Identifier + $5.v as BooleanValueExpression + }) + } + +%% diff --git a/vsql/std_start_transaction_statement.v b/vsql/std_17_1_start_transaction_statement.v similarity index 87% rename from vsql/std_start_transaction_statement.v rename to vsql/std_17_1_start_transaction_statement.v index a8a5d9f..d4cf482 100644 --- a/vsql/std_start_transaction_statement.v +++ b/vsql/std_17_1_start_transaction_statement.v @@ -4,22 +4,11 @@ import time // ISO/IEC 9075-2:2016(E), 17.1, // -// # Function -// // Start an SQL-transaction and set its characteristics. -// -// # Format -//~ -//~ /* Stmt */ ::= -//~ START TRANSACTION -> start_transaction_statement struct StartTransactionStatement { } -fn parse_start_transaction_statement() !Stmt { - return StartTransactionStatement{} -} - fn (stmt StartTransactionStatement) execute(mut conn Connection, params map[string]Value, elapsed_parse time.Duration) !Result { t := start_timer() mut catalog := conn.catalog() diff --git a/vsql/std_17_1_start_transaction_statement.y b/vsql/std_17_1_start_transaction_statement.y new file mode 100644 index 0000000..fce0a31 --- /dev/null +++ b/vsql/std_17_1_start_transaction_statement.y @@ -0,0 +1,6 @@ +%% + +start_transaction_statement: + START TRANSACTION { $$.v = Stmt(StartTransactionStatement{}) } + +%% diff --git a/vsql/std_commit_statement.v b/vsql/std_17_7_commit_statement.v similarity index 88% rename from vsql/std_commit_statement.v rename to vsql/std_17_7_commit_statement.v index a6144d3..65ff8bd 100644 --- a/vsql/std_commit_statement.v +++ b/vsql/std_17_7_commit_statement.v @@ -4,23 +4,11 @@ import time // ISO/IEC 9075-2:2016(E), 17.7, // -// # Function -// // Terminate the current SQL-transaction with commit. -// -// # Format -//~ -//~ /* Stmt */ ::= -//~ COMMIT -> commit -//~ | COMMIT WORK -> commit struct CommitStatement { } -fn parse_commit() !Stmt { - return CommitStatement{} -} - fn (stmt CommitStatement) execute(mut conn Connection, params map[string]Value, elapsed_parse time.Duration) !Result { t := start_timer() mut catalog := conn.catalog() diff --git a/vsql/std_17_7_commit_statement.y b/vsql/std_17_7_commit_statement.y new file mode 100644 index 0000000..29aa910 --- /dev/null +++ b/vsql/std_17_7_commit_statement.y @@ -0,0 +1,7 @@ +%% + +commit_statement: + COMMIT { $$.v = Stmt(CommitStatement{}) } +| COMMIT WORK { $$.v = Stmt(CommitStatement{}) } + +%% diff --git a/vsql/std_rollback_statement.v b/vsql/std_17_8_rollback_statement.v similarity index 88% rename from vsql/std_rollback_statement.v rename to vsql/std_17_8_rollback_statement.v index 2ae8138..89b8382 100644 --- a/vsql/std_rollback_statement.v +++ b/vsql/std_17_8_rollback_statement.v @@ -4,24 +4,12 @@ import time // ISO/IEC 9075-2:2016(E), 17.8, // -// # Function -// // Terminate the current SQL-transaction with rollback, or rollback all actions // affecting SQL-data and/or schemas since the establishment of a savepoint. -// -// # Format -//~ -//~ /* Stmt */ ::= -//~ ROLLBACK -> rollback -//~ | ROLLBACK WORK -> rollback struct RollbackStatement { } -fn parse_rollback() !Stmt { - return RollbackStatement{} -} - fn (stmt RollbackStatement) execute(mut conn Connection, params map[string]Value, elapsed_parse time.Duration) !Result { t := start_timer() mut catalog := conn.catalog() diff --git a/vsql/std_17_8_rollback_statement.y b/vsql/std_17_8_rollback_statement.y new file mode 100644 index 0000000..098686b --- /dev/null +++ b/vsql/std_17_8_rollback_statement.y @@ -0,0 +1,7 @@ +%% + +rollback_statement: + ROLLBACK { $$.v = Stmt(RollbackStatement{}) } +| ROLLBACK WORK { $$.v = Stmt(RollbackStatement{}) } + +%% diff --git a/vsql/std_set_catalog_statement.v b/vsql/std_19_5_set_catalog_statement.v similarity index 71% rename from vsql/std_set_catalog_statement.v rename to vsql/std_19_5_set_catalog_statement.v index 71eb7d3..73ac6c1 100644 --- a/vsql/std_set_catalog_statement.v +++ b/vsql/std_19_5_set_catalog_statement.v @@ -4,20 +4,10 @@ import time // ISO/IEC 9075-2:2016(E), 19.5, // -// # Function -// // Set the default catalog name for unqualified s in // s that are prepared in the current SQL-session by an // or a and in // s that are invoked directly. -// -// # Format -//~ -//~ /* Stmt */ ::= -//~ SET -> set_catalog_stmt -//~ -//~ /* ValueSpecification */ ::= -//~ CATALOG -> catalog_name_characteristic struct SetCatalogStatement { catalog_name ValueSpecification @@ -27,21 +17,13 @@ fn (e SetCatalogStatement) pstr(params map[string]Value) string { return 'SET CATALOG ${e.catalog_name.pstr(params)}' } -fn parse_set_catalog_stmt(catalog_name ValueSpecification) !Stmt { - return SetCatalogStatement{catalog_name} -} - -fn parse_catalog_name_characteristic(v ValueSpecification) !ValueSpecification { - return v -} - fn (stmt SetCatalogStatement) execute(mut conn Connection, params map[string]Value, elapsed_parse time.Duration) !Result { t := start_timer() // This does not need to hold a write connection with the file. mut c := Compiler{ - conn: conn + conn: conn params: params } new_catalog := stmt.catalog_name.compile(mut c)!.run(mut conn, Row{}, map[string]Value{})!.str() diff --git a/vsql/std_19_5_set_catalog_statement.y b/vsql/std_19_5_set_catalog_statement.y new file mode 100644 index 0000000..573c81f --- /dev/null +++ b/vsql/std_19_5_set_catalog_statement.y @@ -0,0 +1,11 @@ +%% + +set_catalog_statement: + SET catalog_name_characteristic { + $$.v = Stmt(SetCatalogStatement{$2.v as ValueSpecification}) + } + +catalog_name_characteristic: + CATALOG value_specification { $$.v = $2.v as ValueSpecification } + +%% diff --git a/vsql/std_set_schema_statement.v b/vsql/std_19_6_set_schema_statement.v similarity index 72% rename from vsql/std_set_schema_statement.v rename to vsql/std_19_6_set_schema_statement.v index ed6d75a..103ed0c 100644 --- a/vsql/std_set_schema_statement.v +++ b/vsql/std_19_6_set_schema_statement.v @@ -4,20 +4,10 @@ import time // ISO/IEC 9075-2:2016(E), 19.6, // -// # Function -// // Set the default schema name for unqualified s in // s that are prepared in the current SQL-session by an // or a and in // s that are invoked directly. -// -// # Format -//~ -//~ /* Stmt */ ::= -//~ SET -> set_schema_stmt -//~ -//~ /* ValueSpecification */ ::= -//~ SCHEMA -> schema_name_characteristic struct SetSchemaStatement { schema_name ValueSpecification @@ -27,21 +17,13 @@ fn (e SetSchemaStatement) pstr(params map[string]Value) string { return 'SET SCHEMA ${e.schema_name.pstr(params)}' } -fn parse_schema_name_characteristic(v ValueSpecification) !ValueSpecification { - return v -} - -fn parse_set_schema_stmt(schema_name ValueSpecification) !Stmt { - return SetSchemaStatement{schema_name} -} - fn (stmt SetSchemaStatement) execute(mut conn Connection, params map[string]Value, elapsed_parse time.Duration) !Result { t := start_timer() // This does not need to hold a write connection with the file. mut c := Compiler{ - conn: conn + conn: conn params: params } new_schema := stmt.schema_name.compile(mut c)!.run(mut conn, Row{}, map[string]Value{})!.str() diff --git a/vsql/std_19_6_set_schema_statement.y b/vsql/std_19_6_set_schema_statement.y new file mode 100644 index 0000000..6286b99 --- /dev/null +++ b/vsql/std_19_6_set_schema_statement.y @@ -0,0 +1,11 @@ +%% + +set_schema_statement: + SET schema_name_characteristic { + $$.v = SetSchemaStatement{$2.v as ValueSpecification} + } + +schema_name_characteristic: + SCHEMA value_specification { $$.v = $2.v } + +%% diff --git a/vsql/std_20_7_prepare_statement.y b/vsql/std_20_7_prepare_statement.y new file mode 100644 index 0000000..581e65d --- /dev/null +++ b/vsql/std_20_7_prepare_statement.y @@ -0,0 +1,31 @@ +%% + +// ISO/IEC 9075-2:2016(E), 20.7, +// +// Prepare a statement for execution. + +preparable_statement: + preparable_sql_data_statement { $$.v = $1.v as Stmt } +| preparable_sql_schema_statement { $$.v = $1.v as Stmt } +| preparable_sql_transaction_statement { $$.v = $1.v as Stmt } +| preparable_sql_session_statement { $$.v = $1.v as Stmt } + +preparable_sql_data_statement: + delete_statement_searched { $$.v = $1.v as Stmt } +| insert_statement { $$.v = $1.v as Stmt } +| dynamic_select_statement { $$.v = $1.v as Stmt } +| update_statement_searched { $$.v = $1.v as Stmt } + +preparable_sql_schema_statement: + sql_schema_statement { $$.v = $1.v as Stmt } + +preparable_sql_transaction_statement: + sql_transaction_statement { $$.v = $1.v as Stmt } + +preparable_sql_session_statement: + sql_session_statement { $$.v = $1.v as Stmt } + +dynamic_select_statement: + cursor_specification { $$.v = $1.v as Stmt } + +%% diff --git a/vsql/std_sqlstate.v b/vsql/std_24_1_sqlstate.v similarity index 79% rename from vsql/std_sqlstate.v rename to vsql/std_24_1_sqlstate.v index b53ed65..9e4fafd 100644 --- a/vsql/std_sqlstate.v +++ b/vsql/std_24_1_sqlstate.v @@ -79,8 +79,8 @@ struct SQLState22001 { fn sqlstate_22001(to Type) IError { return SQLState22001{ code: sqlstate_to_int('22001') - msg: 'string data right truncation for ${to}' - to: to + msg: 'string data right truncation for ${to}' + to: to } } @@ -92,7 +92,7 @@ struct SQLState22003 { fn sqlstate_22003() IError { return SQLState22003{ code: sqlstate_to_int('22003') - msg: 'numeric value out of range' + msg: 'numeric value out of range' } } @@ -105,7 +105,7 @@ struct SQLState2200H { fn sqlstate_2200h(generator_name string) IError { return SQLState2200H{ code: sqlstate_to_int('2200H') - msg: 'sequence generator limit exceeded: ${generator_name}' + msg: 'sequence generator limit exceeded: ${generator_name}' } } @@ -117,7 +117,7 @@ struct SQLState22012 { fn sqlstate_22012() IError { return SQLState22012{ code: sqlstate_to_int('22012') - msg: 'division by zero' + msg: 'division by zero' } } @@ -129,7 +129,7 @@ struct SQLState23502 { fn sqlstate_23502(msg string) IError { return SQLState23502{ code: sqlstate_to_int('23502') - msg: 'violates non-null constraint: ${msg}' + msg: 'violates non-null constraint: ${msg}' } } @@ -142,8 +142,8 @@ pub: fn sqlstate_2bp01(object_name string) IError { return SQLState2BP01{ - code: sqlstate_to_int('2BP01') - msg: 'dependent objects still exist on ${object_name}' + code: sqlstate_to_int('2BP01') + msg: 'dependent objects still exist on ${object_name}' object_name: object_name } } @@ -157,8 +157,8 @@ pub: fn sqlstate_3d000(catalog_name string) IError { return SQLState3D000{ - code: sqlstate_to_int('3D000') - msg: 'invalid catalog name: ${catalog_name}' + code: sqlstate_to_int('3D000') + msg: 'invalid catalog name: ${catalog_name}' catalog_name: catalog_name } } @@ -172,8 +172,8 @@ pub: fn sqlstate_3f000(schema_name string) IError { return SQLState3F000{ - code: sqlstate_to_int('3F000') - msg: 'invalid schema name: ${schema_name}' + code: sqlstate_to_int('3F000') + msg: 'invalid schema name: ${schema_name}' schema_name: schema_name } } @@ -186,7 +186,7 @@ struct SQLState42601 { fn sqlstate_42601(message string) IError { return SQLState42601{ code: sqlstate_to_int('42601') - msg: 'syntax error: ${message}' + msg: 'syntax error: ${message}' } } @@ -199,8 +199,8 @@ pub: fn sqlstate_42703(column_name string) IError { return SQLState42703{ - code: sqlstate_to_int('42703') - msg: 'no such column: ${column_name}' + code: sqlstate_to_int('42703') + msg: 'no such column: ${column_name}' column_name: column_name } } @@ -214,10 +214,10 @@ struct SQLState42804 { fn sqlstate_42804(msg string, expected string, actual string) IError { return SQLState42804{ - code: sqlstate_to_int('42804') - msg: 'data type mismatch ${msg}: expected ${expected} but got ${actual}' + code: sqlstate_to_int('42804') + msg: 'data type mismatch ${msg}: expected ${expected} but got ${actual}' expected: expected - actual: actual + actual: actual } } @@ -231,9 +231,9 @@ struct SQLState42846 { fn sqlstate_42846(from Type, to Type) IError { return SQLState42846{ code: sqlstate_to_int('42846') - msg: 'cannot coerce ${from} to ${to}' + msg: 'cannot coerce ${from} to ${to}' from: from - to: to + to: to } } @@ -249,8 +249,8 @@ pub: fn sqlstate_42p01(entity_type string, entity_name string) IError { return SQLState42P01{ - code: sqlstate_to_int('42P01') - msg: 'no such ${entity_type}: ${entity_name}' + code: sqlstate_to_int('42P01') + msg: 'no such ${entity_type}: ${entity_name}' entity_type: entity_type entity_name: entity_name } @@ -265,8 +265,8 @@ pub: fn sqlstate_42p06(schema_name string) IError { return SQLState42P06{ - code: sqlstate_to_int('42P06') - msg: 'duplicate schema: ${schema_name}' + code: sqlstate_to_int('42P06') + msg: 'duplicate schema: ${schema_name}' schema_name: schema_name } } @@ -280,8 +280,8 @@ pub: fn sqlstate_42p07(table_name string) IError { return SQLState42P07{ - code: sqlstate_to_int('42P07') - msg: 'duplicate table: ${table_name}' + code: sqlstate_to_int('42P07') + msg: 'duplicate table: ${table_name}' table_name: table_name } } @@ -294,7 +294,7 @@ struct SQLState42883 { fn sqlstate_42883(msg string) IError { return SQLState42883{ code: sqlstate_to_int('42883') - msg: msg + msg: msg } } @@ -307,8 +307,8 @@ pub: fn sqlstate_42p02(parameter_name string) IError { return SQLState42P02{ - code: sqlstate_to_int('42P02') - msg: 'parameter does not exist: ${parameter_name}' + code: sqlstate_to_int('42P02') + msg: 'parameter does not exist: ${parameter_name}' parameter_name: parameter_name } } @@ -321,7 +321,7 @@ struct SQLState25001 { fn sqlstate_25001() IError { return SQLState25001{ code: sqlstate_to_int('25001') - msg: 'invalid transaction state: active sql transaction' + msg: 'invalid transaction state: active sql transaction' } } @@ -333,7 +333,7 @@ struct SQLState2D000 { fn sqlstate_2d000() IError { return SQLState2D000{ code: sqlstate_to_int('2D000') - msg: 'invalid transaction termination' + msg: 'invalid transaction termination' } } @@ -345,7 +345,7 @@ struct SQLState0B000 { fn sqlstate_0b000(msg string) IError { return SQLState0B000{ code: sqlstate_to_int('0B000') - msg: 'invalid transaction initiation: ${msg}' + msg: 'invalid transaction initiation: ${msg}' } } @@ -357,7 +357,7 @@ struct SQLState40001 { fn sqlstate_40001(message string) IError { return SQLState40001{ code: sqlstate_to_int('40001') - msg: 'serialization failure: ${message}' + msg: 'serialization failure: ${message}' } } @@ -369,6 +369,6 @@ struct SQLState25P02 { fn sqlstate_25p02() IError { return SQLState25P02{ code: sqlstate_to_int('25P02') - msg: 'transaction is aborted, commands ignored until end of transaction block' + msg: 'transaction is aborted, commands ignored until end of transaction block' } } diff --git a/vsql/std_sql_schemas.v b/vsql/std_4_26_sql_schemas.v similarity index 100% rename from vsql/std_sql_schemas.v rename to vsql/std_4_26_sql_schemas.v diff --git a/vsql/std_sequence_generators.v b/vsql/std_4_27_sequence_generators.v similarity index 100% rename from vsql/std_sequence_generators.v rename to vsql/std_4_27_sequence_generators.v diff --git a/vsql/std_5_1_sql_terminal_character.y b/vsql/std_5_1_sql_terminal_character.y new file mode 100644 index 0000000..e83d1cc --- /dev/null +++ b/vsql/std_5_1_sql_terminal_character.y @@ -0,0 +1,31 @@ +%% + +// ISO/IEC 9075-2:2016(E), 5.1, +// +// Define the terminal symbols of the SQL language and the elements of strings. + +left_paren: OPERATOR_LEFT_PAREN + +right_paren: OPERATOR_RIGHT_PAREN + +asterisk: OPERATOR_ASTERISK { $$.v = $1.v as string } + +plus_sign: OPERATOR_PLUS { $$.v = $1.v as string } + +comma: OPERATOR_COMMA + +minus_sign: OPERATOR_MINUS { $$.v = $1.v as string } + +period: OPERATOR_PERIOD + +solidus: OPERATOR_SOLIDUS { $$.v = $1.v as string } + +colon: OPERATOR_COLON + +less_than_operator: OPERATOR_LESS_THAN + +equals_operator: OPERATOR_EQUALS + +greater_than_operator: OPERATOR_GREATER_THAN + +%% diff --git a/vsql/std_5_2_token_and_separator.y b/vsql/std_5_2_token_and_separator.y new file mode 100644 index 0000000..51b26ce --- /dev/null +++ b/vsql/std_5_2_token_and_separator.y @@ -0,0 +1,304 @@ +%% + +concatenation_operator: + OPERATOR_DOUBLE_PIPE + +regular_identifier: + identifier_body { $$.v = $1.v as IdentifierChain } +| non_reserved_word { $$.v = IdentifierChain{$1.v as string} } + +identifier_body: + identifier_start { $$.v = $1.v as IdentifierChain } + +identifier_start: + LITERAL_IDENTIFIER { $$.v = $1.v as IdentifierChain } + +not_equals_operator: OPERATOR_NOT_EQUALS + +greater_than_or_equals_operator: OPERATOR_GREATER_EQUALS + +less_than_or_equals_operator: OPERATOR_LESS_EQUALS + +non_reserved_word: + A +| ABSOLUTE +| ACTION +| ADA +| ADD +| ADMIN +| AFTER +| ALWAYS +| ASC +| ASSERTION +| ASSIGNMENT +| ATTRIBUTE +| ATTRIBUTES +| BEFORE +| BERNOULLI +| BREADTH +| C +| CASCADE +| CATALOG +| CATALOG_NAME +| CHAIN +| CHAINING +| CHARACTER_SET_CATALOG +| CHARACTER_SET_NAME +| CHARACTER_SET_SCHEMA +| CHARACTERISTICS +| CHARACTERS +| CLASS_ORIGIN +| COBOL +| COLLATION +| COLLATION_CATALOG +| COLLATION_NAME +| COLLATION_SCHEMA +| COLUMNS +| COLUMN_NAME +| COMMAND_FUNCTION +| COMMAND_FUNCTION_CODE +| COMMITTED +| CONDITIONAL +| CONDITION_NUMBER +| CONNECTION +| CONNECTION_NAME +| CONSTRAINT_CATALOG +| CONSTRAINT_NAME +| CONSTRAINT_SCHEMA +| CONSTRAINTS +| CONSTRUCTOR +| CONTINUE +| CURSOR_NAME +| DATA +| DATETIME_INTERVAL_CODE +| DATETIME_INTERVAL_PRECISION +| DEFAULTS +| DEFERRABLE +| DEFERRED +| DEFINED +| DEFINER +| DEGREE +| DEPTH +| DERIVED +| DESC +| DESCRIBE_CATALOG +| DESCRIBE_NAME +| DESCRIBE_PROCEDURE_SPECIFIC_CATALOG +| DESCRIBE_PROCEDURE_SPECIFIC_NAME +| DESCRIBE_PROCEDURE_SPECIFIC_SCHEMA +| DESCRIBE_SCHEMA +| DESCRIPTOR +| DIAGNOSTICS +| DISPATCH +| DOMAIN +| DYNAMIC_FUNCTION +| DYNAMIC_FUNCTION_CODE +| ENCODING +| ENFORCED +| ERROR +| EXCLUDE +| EXCLUDING +| EXPRESSION +| FINAL +| FINISH +| FINISH_CATALOG +| FINISH_NAME +| FINISH_PROCEDURE_SPECIFIC_CATALOG +| FINISH_PROCEDURE_SPECIFIC_NAME +| FINISH_PROCEDURE_SPECIFIC_SCHEMA +| FINISH_SCHEMA +| FIRST +| FLAG +| FOLLOWING +| FORMAT +| FORTRAN +| FOUND +| FULFILL +| FULFILL_CATALOG +| FULFILL_NAME +| FULFILL_PROCEDURE_SPECIFIC_CATALOG +| FULFILL_PROCEDURE_SPECIFIC_NAME +| FULFILL_PROCEDURE_SPECIFIC_SCHEMA +| FULFILL_SCHEMA +| G +| GENERAL +| GENERATED +| GO +| GOTO +| GRANTED +| HAS_PASS_THROUGH_COLUMNS +| HAS_PASS_THRU_COLS +| HIERARCHY +| IGNORE +| IMMEDIATE +| IMMEDIATELY +| IMPLEMENTATION +| INCLUDING +| INCREMENT +| INITIALLY +| INPUT +| INSTANCE +| INSTANTIABLE +| INSTEAD +| INVOKER +| ISOLATION +| IS_PRUNABLE +| JSON +| K +| KEEP +| KEY +| KEYS +| KEY_MEMBER +| KEY_TYPE +| LAST +| LENGTH +| LEVEL +| LOCATOR +| M +| MAP +| MATCHED +| MAXVALUE +| MESSAGE_LENGTH +| MESSAGE_OCTET_LENGTH +| MESSAGE_TEXT +| MINVALUE +| MORE +| MUMPS +| NAME +| NAMES +| NESTED +| NESTING +| NEXT +| NFC +| NFD +| NFKC +| NFKD +| NORMALIZED +| NULLABLE +| NULLS +| NUMBER +| OBJECT +| OCTETS +| OPTION +| OPTIONS +| ORDERING +| ORDINALITY +| OTHERS +| OUTPUT +| OVERFLOW +| OVERRIDING +| P +| PAD +| PARAMETER_MODE +| PARAMETER_NAME +| PARAMETER_ORDINAL_POSITION +| PARAMETER_SPECIFIC_CATALOG +| PARAMETER_SPECIFIC_NAME +| PARAMETER_SPECIFIC_SCHEMA +| PARTIAL +| PASCAL +| PASS +| PASSING +| PAST +| PATH +| PLACING +| PLAN +| PLI +| PRECEDING +| PRESERVE +| PRIOR +| PRIVATE +| PRIVATE_PARAMETERS +| PRIVATE_PARAMS_S +| PRIVILEGES +| PRUNE +| PUBLIC +| QUOTES +| READ +| RELATIVE +| REPEATABLE +| RESPECT +| RESTART +| RESTRICT +| RETURNED_CARDINALITY +| RETURNED_LENGTH +| RETURNED_OCTET_LENGTH +| RETURNED_SQLSTATE +| RETURNING +| RETURNS_ONLY_PASS_THROUGH +| RET_ONLY_PASS_THRU +| ROLE +| ROUTINE +| ROUTINE_CATALOG +| ROUTINE_NAME +| ROUTINE_SCHEMA +| ROW_COUNT +| SCALAR +| SCALE +| SCHEMA +| SCHEMA_NAME +| SCOPE_CATALOG +| SCOPE_NAME +| SCOPE_SCHEMA +| SECTION +| SECURITY +| SELF +| SEQUENCE +| SERIALIZABLE +| SERVER_NAME +| SESSION +| SETS +| SIMPLE +| SIZE +| SOURCE +| SPACE +| SPECIFIC_NAME +| START_CATALOG +| START_NAME +| START_PROCEDURE_SPECIFIC_CATALOG +| START_PROCEDURE_SPECIFIC_NAME +| START_PROCEDURE_SPECIFIC_SCHEMA +| START_SCHEMA +| STATE +| STATEMENT +| STRING +| STRUCTURE +| STYLE +| SUBCLASS_ORIGIN +| T +| TABLE_NAME +| TABLE_SEMANTICS +| TEMPORARY +| THROUGH +| TIES +| TOP_LEVEL_COUNT +| TRANSACTION +| TRANSACTION_ACTIVE +| TRANSACTIONS_COMMITTED +| TRANSACTIONS_ROLLED_BACK +| TRANSFORM +| TRANSFORMS +| TRIGGER_CATALOG +| TRIGGER_NAME +| TRIGGER_SCHEMA +| TYPE +| UNBOUNDED +| UNCOMMITTED +| UNCONDITIONAL +| UNDER +| UNNAMED +| USAGE +| USER_DEFINED_TYPE_CATALOG +| USER_DEFINED_TYPE_CODE +| USER_DEFINED_TYPE_NAME +| USER_DEFINED_TYPE_SCHEMA +| UTF16 +| UTF32 +| UTF8 +| VIEW +| WORK +| WRAPPER +| WRITE +| ZONE + +%% diff --git a/vsql/std_5_3_literal.v b/vsql/std_5_3_literal.v new file mode 100644 index 0000000..db3fb29 --- /dev/null +++ b/vsql/std_5_3_literal.v @@ -0,0 +1,38 @@ +module vsql + +import math.big + +// ISO/IEC 9075-2:2016(E), 5.3, +// +// Specify a non-null value. + +fn numeric_literal(x string) !Value { + // Any number that contains a decimal (even if its a whole number) must be + // treated as a NUMERIC. + if x.contains('.') { + // The trim handles cases of "123." which should be treated as "123". + return new_numeric_value(x.trim_right('.')) + } + + // Otherwise, we know this is an int but we have to choose the smallest type. + // + // Note: There is an edge case where the negative sign may be consumed as part + // of rather than . See parse_factor_2() for + // those edge cases. + n := big.integer_from_string(x)! + + if n >= big.integer_from_i64(-32768) && n <= big.integer_from_i64(32767) { + return new_smallint_value(i16(x.i64())) + } + + if n >= big.integer_from_i64(-2147483648) && n <= big.integer_from_i64(2147483647) { + return new_integer_value(int(x.i64())) + } + + if n >= big.integer_from_i64(-9223372036854775808) + && n <= big.integer_from_i64(9223372036854775807) { + return new_bigint_value(x.i64()) + } + + return new_numeric_value(x) +} diff --git a/vsql/std_5_3_literal.y b/vsql/std_5_3_literal.y new file mode 100644 index 0000000..4ac634e --- /dev/null +++ b/vsql/std_5_3_literal.y @@ -0,0 +1,96 @@ +%% + +literal: + signed_numeric_literal { $$.v = $1.v as Value } +| general_literal { $$.v = $1.v as Value } + +unsigned_literal: + unsigned_numeric_literal { $$.v = $1.v as Value } +| general_literal { $$.v = $1.v as Value } + +general_literal: + character_string_literal { $$.v = $1.v as Value } +| datetime_literal { $$.v = $1.v as Value } +| boolean_literal { $$.v = $1.v as Value } + +character_string_literal: + LITERAL_STRING { $$.v = $1.v as Value } + +signed_numeric_literal: + unsigned_numeric_literal { $$.v = $1.v as Value } +| sign unsigned_numeric_literal { + $$.v = numeric_literal($1.v as string + ($2.v as Value).str())! + } + +unsigned_numeric_literal: + exact_numeric_literal { $$.v = $1.v as Value } +| approximate_numeric_literal { $$.v = $1.v as Value } + +exact_numeric_literal: + unsigned_integer { $$.v = numeric_literal($1.v as string)! } +| unsigned_integer period { $$.v = numeric_literal(($1.v as string) + '.')! } +| unsigned_integer period unsigned_integer { + $$.v = numeric_literal(($1.v as string) + '.' + ($3.v as string))! + } +| period unsigned_integer { $$.v = numeric_literal('0.' + ($2.v as string))! } + +sign: + plus_sign { $$.v = $1.v as string } +| minus_sign { $$.v = $1.v as string } + +approximate_numeric_literal: + mantissa E exponent { + $$.v = new_double_precision_value( + ($1.v as Value).as_f64()! * math.pow(10, ($3.v as Value).as_f64()!)) + } + +mantissa: + exact_numeric_literal { $$.v = $1.v as Value } + +exponent: + signed_integer { $$.v = $1.v as Value } + +signed_integer: + unsigned_integer { $$.v = new_numeric_value($1.v as string) } +| sign unsigned_integer { + $$.v = if $1.v as string == '-' { + new_numeric_value('-' + ($2.v as string)) + } else { + new_numeric_value($2.v as string) + } + } + +unsigned_integer: + LITERAL_NUMBER { $$.v = $1.v as string } + +datetime_literal: + date_literal { $$.v = $1.v as Value } +| time_literal { $$.v = $1.v as Value } +| timestamp_literal { $$.v = $1.v as Value } + +date_literal: + DATE date_string { $$.v = new_date_value(($2.v as Value).string_value())! } + +time_literal: + TIME time_string { $$.v = new_time_value(($2.v as Value).string_value())! } + +timestamp_literal: + TIMESTAMP timestamp_string { + $$.v = new_timestamp_value(($2.v as Value).string_value())! + } + +date_string: + LITERAL_STRING { $$.v = $1.v as Value } + +time_string: + LITERAL_STRING { $$.v = $1.v as Value } + +timestamp_string: + LITERAL_STRING { $$.v = $1.v as Value } + +boolean_literal: + TRUE { $$.v = new_boolean_value(true) } +| FALSE { $$.v = new_boolean_value(false) } +| UNKNOWN { $$.v = new_unknown_value() } + +%% diff --git a/vsql/std_names_and_identifiers.v b/vsql/std_5_4_names_and_identifiers.v similarity index 73% rename from vsql/std_names_and_identifiers.v rename to vsql/std_5_4_names_and_identifiers.v index 9ec7f3b..df809b5 100644 --- a/vsql/std_names_and_identifiers.v +++ b/vsql/std_5_4_names_and_identifiers.v @@ -2,57 +2,7 @@ module vsql // ISO/IEC 9075-2:2016(E), 5.4, Names and identifiers // -// # Function -// // Specify names. -// -// # Format -//~ -//~ /* IdentifierChain */ ::= -//~ -//~ -//~ /* IdentifierChain */ ::= -//~ -//~ -//~
    /* Identifier */ ::= -//~ -> table_name -//~ -//~ /* Identifier */ ::= -//~ -> schema_name_1 -//~ | -//~ -//~ /* Identifier */ ::= -//~ -> unqualified_schema_name -//~ -//~ /* IdentifierChain */ ::= -//~ -//~ -//~ /* IdentifierChain */ ::= -//~ -//~ | -> schema_qualified_name_2 -//~ -//~ /* IdentifierChain */ ::= -//~ -//~ | -//~ -> local_or_schema_qualified_name2 -//~ -//~ /* Identifier */ ::= -//~ -//~ -//~ /* IdentifierChain */ ::= -//~ -//~ -//~ /* Identifier */ ::= -//~ -> column_name -//~ -//~ /* GeneralValueSpecification */ ::= -//~ -> host_parameter_name -//~ -//~ /* Identifier */ ::= -//~ -> correlation_name -//~ -//~ /* Identifier */ ::= -//~ -> sequence_generator_name // Identifier is used to describe a object within a schema (such as a table // name) or a property of an object (like a column name of a table). You should @@ -138,7 +88,7 @@ fn new_identifier1(s string) !Identifier { 2 { return Identifier{ catalog_name: parts[0] - schema_name: parts[1] + schema_name: parts[1] } } else { @@ -165,8 +115,8 @@ fn new_identifier2(s string) !Identifier { 3 { return Identifier{ catalog_name: parts[0] - schema_name: parts[1] - entity_name: parts[2] + schema_name: parts[1] + entity_name: parts[2] } } else { @@ -209,22 +159,22 @@ fn new_identifier3(s string) !Identifier { } 2 { return Identifier{ - entity_name: parts[0] + entity_name: parts[0] sub_entity_name: parts[1] } } 3 { return Identifier{ - schema_name: parts[0] - entity_name: parts[1] + schema_name: parts[0] + entity_name: parts[1] sub_entity_name: parts[2] } } 4 { return Identifier{ - catalog_name: parts[0] - schema_name: parts[1] - entity_name: parts[2] + catalog_name: parts[0] + schema_name: parts[1] + entity_name: parts[2] sub_entity_name: parts[3] } } @@ -289,10 +239,10 @@ fn (e Identifier) compile(mut c Compiler) !CompileResult { // removed in the future. if e.custom_id != '' { return CompileResult{ - run: fn [e] (mut conn Connection, data Row, params map[string]Value) !Value { + run: fn [e] (mut conn Connection, data Row, params map[string]Value) !Value { return data.data[e.id()] or { return sqlstate_42601('unknown column: ${e}') } } - typ: e.custom_typ + typ: e.custom_typ contains_agg: false } } @@ -302,10 +252,10 @@ fn (e Identifier) compile(mut c Compiler) !CompileResult { column := table.column(e.sub_entity_name) or { Column{} } if column.name.sub_entity_name == e.sub_entity_name { return CompileResult{ - run: fn [e] (mut conn Connection, data Row, params map[string]Value) !Value { + run: fn [e] (mut conn Connection, data Row, params map[string]Value) !Value { return data.data[e.id()] or { return sqlstate_42601('unknown column: ${e}') } } - typ: column.typ + typ: column.typ contains_agg: false } } @@ -313,13 +263,21 @@ fn (e Identifier) compile(mut c Compiler) !CompileResult { // 3. Try to use the context. mut ident := c.conn.resolve_identifier(Identifier{ - catalog_name: if c.context.catalog_name != '' { + catalog_name: if c.context.catalog_name != '' { c.context.catalog_name } else { e.catalog_name } - schema_name: if c.context.schema_name != '' { c.context.schema_name } else { e.schema_name } - entity_name: if c.context.entity_name != '' { c.context.entity_name } else { e.entity_name } + schema_name: if c.context.schema_name != '' { + c.context.schema_name + } else { + e.schema_name + } + entity_name: if c.context.entity_name != '' { + c.context.entity_name + } else { + e.entity_name + } sub_entity_name: e.sub_entity_name }) mut catalog := c.conn.catalogs[ident.catalog_name] or { @@ -331,12 +289,12 @@ fn (e Identifier) compile(mut c Compiler) !CompileResult { column := table.column(ident.sub_entity_name) or { Column{} } if column.name.sub_entity_name == ident.sub_entity_name { return CompileResult{ - run: fn [ident] (mut conn Connection, data Row, params map[string]Value) !Value { + run: fn [ident] (mut conn Connection, data Row, params map[string]Value) !Value { return data.data[ident.id()] or { return sqlstate_42601('unknown column: ${ident}') } } - typ: column.typ + typ: column.typ contains_agg: false } } @@ -351,12 +309,12 @@ fn (e Identifier) compile(mut c Compiler) !CompileResult { ident = column.name return CompileResult{ - run: fn [ident] (mut conn Connection, data Row, params map[string]Value) !Value { + run: fn [ident] (mut conn Connection, data Row, params map[string]Value) !Value { return data.data[ident.id()] or { return sqlstate_42601('unknown column: ${ident}') } } - typ: column.typ + typ: column.typ contains_agg: false } } @@ -370,12 +328,12 @@ fn (e Identifier) compile(mut c Compiler) !CompileResult { column := table.column(ident.sub_entity_name) or { Column{} } if column.name.sub_entity_name == ident.sub_entity_name { return CompileResult{ - run: fn [ident] (mut conn Connection, data Row, params map[string]Value) !Value { + run: fn [ident] (mut conn Connection, data Row, params map[string]Value) !Value { return data.data[ident.id()] or { return sqlstate_42601('unknown column: ${ident}') } } - typ: column.typ + typ: column.typ contains_agg: false } } @@ -393,12 +351,12 @@ fn (e Identifier) compile(mut c Compiler) !CompileResult { ident = column.name return CompileResult{ - run: fn [ident] (mut conn Connection, data Row, params map[string]Value) !Value { + run: fn [ident] (mut conn Connection, data Row, params map[string]Value) !Value { return data.data[ident.id()] or { return sqlstate_42601('unknown column: ${ident}') } } - typ: column.typ + typ: column.typ contains_agg: false } } @@ -513,42 +471,10 @@ fn (e HostParameterName) compile(mut c Compiler) !CompileResult { p := c.params[e.name] or { return sqlstate_42p02(e.name) } return CompileResult{ - run: fn [p] (mut conn Connection, data Row, params map[string]Value) !Value { + run: fn [p] (mut conn Connection, data Row, params map[string]Value) !Value { return p } - typ: p.typ + typ: p.typ contains_agg: false } } - -fn parse_table_name(identifier IdentifierChain) !Identifier { - return new_table_identifier(identifier.identifier) -} - -fn parse_schema_name_1(catalog IdentifierChain, identifier Identifier) !Identifier { - return new_schema_identifier('${catalog}.${identifier}') -} - -fn parse_unqualified_schema_name(identifier IdentifierChain) !Identifier { - return new_schema_identifier(identifier.identifier) -} - -fn parse_schema_qualified_name_2(schema_name Identifier, identifier IdentifierChain) !IdentifierChain { - return IdentifierChain{'${schema_name.schema_name}.${identifier}'} -} - -fn parse_local_or_schema_qualified_name2(schema_name Identifier, table_name IdentifierChain) !IdentifierChain { - return IdentifierChain{'${schema_name}.${table_name}'} -} - -fn parse_column_name(column_name IdentifierChain) !Identifier { - return new_column_identifier(column_name.identifier) -} - -fn parse_host_parameter_name(name IdentifierChain) !GeneralValueSpecification { - return HostParameterName{name.identifier} -} - -fn parse_correlation_name(identifier IdentifierChain) !Identifier { - return new_column_identifier(identifier.identifier) -} diff --git a/vsql/std_5_4_names_and_identifiers.y b/vsql/std_5_4_names_and_identifiers.y new file mode 100644 index 0000000..f01cd21 --- /dev/null +++ b/vsql/std_5_4_names_and_identifiers.y @@ -0,0 +1,71 @@ +%% + +identifier: + actual_identifier { $$.v = $1.v as IdentifierChain } + +actual_identifier: + regular_identifier { $$.v = $1.v as IdentifierChain } + +table_name: + local_or_schema_qualified_name { + $$.v = new_table_identifier(($1.v as IdentifierChain).identifier)! + } + +schema_name: + catalog_name period unqualified_schema_name { + $$.v = new_schema_identifier(($1.v as IdentifierChain).str() + '.' + + ($3.v as Identifier).str())! + } +| unqualified_schema_name { $$.v = $1.v as Identifier } + +unqualified_schema_name: + identifier { + $$.v = new_schema_identifier(($1.v as IdentifierChain).identifier)! + } + +catalog_name: + identifier { $$.v = $1.v as IdentifierChain } + +schema_qualified_name: + qualified_identifier { $$.v = $1.v as IdentifierChain } +| schema_name period qualified_identifier { + $$.v = IdentifierChain{($1.v as Identifier).schema_name + '.' + + ($3.v as IdentifierChain).str()} + } + +local_or_schema_qualified_name: + qualified_identifier { $$.v = $1.v as IdentifierChain } +| local_or_schema_qualifier period qualified_identifier { + $$.v = IdentifierChain{($1.v as Identifier).str() + '.' + + ($3.v as IdentifierChain).str()} + } + +local_or_schema_qualifier: + schema_name { $$.v = $1.v as Identifier } + +qualified_identifier: + identifier { $$.v = $1.v as IdentifierChain } + +column_name: + identifier { + $$.v = new_column_identifier(($1.v as IdentifierChain).identifier)! + } + +host_parameter_name: + colon identifier { + $$.v = GeneralValueSpecification(HostParameterName{ + ($2.v as IdentifierChain).identifier} + ) + } + +correlation_name: + identifier { + $$.v = new_column_identifier(($1.v as IdentifierChain).identifier)! + } + +sequence_generator_name: + schema_qualified_name { + $$.v = new_table_identifier(($1.v as IdentifierChain).identifier)! + } + +%% diff --git a/vsql/std_case_expression.v b/vsql/std_6_12_case_expression.v similarity index 59% rename from vsql/std_case_expression.v rename to vsql/std_6_12_case_expression.v index 6ad7f86..a7fb079 100644 --- a/vsql/std_case_expression.v +++ b/vsql/std_6_12_case_expression.v @@ -2,25 +2,7 @@ module vsql // ISO/IEC 9075-2:2016(E), 6.12, // -// # Function -// // Specify a conditional value. -// -// # Format -//~ -//~ /* CaseExpression */ ::= -//~ -//~ -//~ /* CaseExpression */ ::= -//~ NULLIF -//~ -> nullif -//~ | COALESCE -> coalesce -// -// These are non-standard, just to simplify standard rules: -//~ -//~ /* []ValueExpression */ ::= -//~ -> value_expression_list_1 -//~ | -> value_expression_list_2 type CaseExpression = CaseExpressionCoalesce | CaseExpressionNullIf @@ -42,7 +24,7 @@ fn (e CaseExpression) compile(mut c Compiler) !CompileResult { compiled_b := e.b.compile(mut c)! return CompileResult{ - run: fn [compiled_a, compiled_b] (mut conn Connection, data Row, params map[string]Value) !Value { + run: fn [compiled_a, compiled_b] (mut conn Connection, data Row, params map[string]Value) !Value { a := compiled_a.run(mut conn, data, params)! b := compiled_b.run(mut conn, data, params)! @@ -57,7 +39,7 @@ fn (e CaseExpression) compile(mut c Compiler) !CompileResult { return a } - typ: compiled_a.typ + typ: compiled_a.typ contains_agg: compiled_a.contains_agg || compiled_b.contains_agg } } @@ -70,7 +52,7 @@ fn (e CaseExpression) compile(mut c Compiler) !CompileResult { } return CompileResult{ - run: fn [compiled_exprs] (mut conn Connection, data Row, params map[string]Value) !Value { + run: fn [compiled_exprs] (mut conn Connection, data Row, params map[string]Value) !Value { mut typ := SQLType.is_varchar mut first := true for i, compiled_expr in compiled_exprs { @@ -96,7 +78,7 @@ fn (e CaseExpression) compile(mut c Compiler) !CompileResult { return new_null_value(value.typ.typ) } - typ: compiled_exprs[0].typ + typ: compiled_exprs[0].typ contains_agg: contains_agg } } @@ -111,22 +93,3 @@ struct CaseExpressionNullIf { struct CaseExpressionCoalesce { exprs []ValueExpression } - -fn parse_nullif(a ValueExpression, b ValueExpression) !CaseExpression { - return CaseExpressionNullIf{a, b} -} - -fn parse_coalesce(exprs []ValueExpression) !CaseExpression { - return CaseExpressionCoalesce{exprs} -} - -fn parse_value_expression_list_1(e ValueExpression) ![]ValueExpression { - return [e] -} - -fn parse_value_expression_list_2(element_list []ValueExpression, element ValueExpression) ![]ValueExpression { - mut new_list := element_list.clone() - new_list << element - - return new_list -} diff --git a/vsql/std_6_12_case_expression.y b/vsql/std_6_12_case_expression.y new file mode 100644 index 0000000..8254dfd --- /dev/null +++ b/vsql/std_6_12_case_expression.y @@ -0,0 +1,25 @@ +%% + +case_expression: + case_abbreviation { $$.v = $1.v as CaseExpression } + +case_abbreviation: + NULLIF left_paren value_expression comma value_expression right_paren { + $$.v = CaseExpression(CaseExpressionNullIf{ + $3.v as ValueExpression + $5.v as ValueExpression + }) + } +| COALESCE left_paren value_expression_list right_paren { + $$.v = CaseExpression(CaseExpressionCoalesce{$3.v as []ValueExpression}) + } + +// These are non-standard, just to simplify standard rules: + +value_expression_list: + value_expression { $$.v = [$1.v as ValueExpression] } +| value_expression_list comma value_expression { + $$.v = append_list($1.v as []ValueExpression, $3.v as ValueExpression) + } + +%% diff --git a/vsql/std_cast_specification.v b/vsql/std_6_13_cast_specification.v similarity index 60% rename from vsql/std_cast_specification.v rename to vsql/std_6_13_cast_specification.v index e4f9443..d5bf1a8 100644 --- a/vsql/std_cast_specification.v +++ b/vsql/std_6_13_cast_specification.v @@ -2,21 +2,7 @@ module vsql // ISO/IEC 9075-2:2016(E), 6.13, // -// # Function -// // Specify a data conversion. -// -// # Format -//~ -//~ /* CastSpecification */ ::= -//~ CAST AS -> cast -//~ -//~ /* CastOperand */ ::= -//~ -> CastOperand -//~ | -> CastOperand -//~ -//~ /* Type */ ::= -//~ type CastOperand = NullSpecification | ValueExpression @@ -49,16 +35,12 @@ fn (e CastSpecification) compile(mut c Compiler) !CompileResult { compiled_expr := e.expr.compile(mut c)! return CompileResult{ - run: fn [e, compiled_expr] (mut conn Connection, data Row, params map[string]Value) !Value { + run: fn [e, compiled_expr] (mut conn Connection, data Row, params map[string]Value) !Value { value := compiled_expr.run(mut conn, data, params)! return cast(mut conn, 'for CAST', value, e.target)! } - typ: e.target + typ: e.target contains_agg: compiled_expr.contains_agg } } - -fn parse_cast(expr CastOperand, typ Type) !CastSpecification { - return CastSpecification{expr, typ} -} diff --git a/vsql/std_6_13_cast_specification.y b/vsql/std_6_13_cast_specification.y new file mode 100644 index 0000000..9e2c797 --- /dev/null +++ b/vsql/std_6_13_cast_specification.y @@ -0,0 +1,17 @@ +%% + +cast_specification: + CAST left_paren cast_operand AS cast_target right_paren { + $$.v = CastSpecification{$3.v as CastOperand, $5.v as Type} + } + +cast_operand: + value_expression { $$.v = CastOperand($1.v as ValueExpression) } +| implicitly_typed_value_specification { + $$.v = CastOperand($1.v as NullSpecification) + } + +cast_target: + data_type { $$.v = $1.v as Type } + +%% diff --git a/vsql/std_next_value_expression.v b/vsql/std_6_14_next_value_expression.v similarity index 65% rename from vsql/std_next_value_expression.v rename to vsql/std_6_14_next_value_expression.v index 9c8384e..9275718 100644 --- a/vsql/std_next_value_expression.v +++ b/vsql/std_6_14_next_value_expression.v @@ -2,14 +2,7 @@ module vsql // ISO/IEC 9075-2:2016(E), 6.14, // -// # Function -// // Return the next value of a sequence generator. -// -// # Format -//~ -//~ /* NextValueExpression */ ::= -//~ NEXT VALUE FOR -> next_value_expression // NextValueExpression for "NEXT VALUE FOR " struct NextValueExpression { @@ -25,12 +18,12 @@ fn (e NextValueExpression) compile(mut c Compiler) !CompileResult { name := c.conn.resolve_identifier(e.name) return CompileResult{ - run: fn [name, mut catalog] (mut conn Connection, data Row, params map[string]Value) !Value { + run: fn [name, mut catalog] (mut conn Connection, data Row, params map[string]Value) !Value { next := catalog.storage.sequence_next_value(name)! return new_bigint_value(next) } - typ: new_type('INTEGER', 0, 0) + typ: new_type('INTEGER', 0, 0) contains_agg: false } } @@ -38,9 +31,3 @@ fn (e NextValueExpression) compile(mut c Compiler) !CompileResult { fn (e NextValueExpression) resolve_identifiers(conn &Connection, tables map[string]Table) !NextValueExpression { return NextValueExpression{conn.resolve_identifier(e.name)} } - -fn parse_next_value_expression(name Identifier) !NextValueExpression { - return NextValueExpression{ - name: name - } -} diff --git a/vsql/std_6_14_next_value_expression.y b/vsql/std_6_14_next_value_expression.y new file mode 100644 index 0000000..81189ef --- /dev/null +++ b/vsql/std_6_14_next_value_expression.y @@ -0,0 +1,10 @@ +%% + +next_value_expression: + NEXT VALUE FOR sequence_generator_name { + $$.v = NextValueExpression{ + name: $4.v as Identifier + } + } + +%% diff --git a/vsql/std_6_1_data_type.v b/vsql/std_6_1_data_type.v new file mode 100644 index 0000000..904eff3 --- /dev/null +++ b/vsql/std_6_1_data_type.v @@ -0,0 +1,21 @@ +module vsql + +// ISO/IEC 9075-2:2016(E), 6.1, +// +// Specify a data type. + +fn parse_timestamp_prec_tz_type(prec string, tz bool) !Type { + if tz { + return new_type('TIMESTAMP WITH TIME ZONE', prec.int(), 0) + } + + return new_type('TIMESTAMP WITHOUT TIME ZONE', prec.int(), 0) +} + +fn parse_time_prec_tz_type(prec string, tz bool) !Type { + if tz { + return new_type('TIME WITH TIME ZONE', prec.int(), 0) + } + + return new_type('TIME WITHOUT TIME ZONE', prec.int(), 0) +} diff --git a/vsql/std_6_1_data_type.y b/vsql/std_6_1_data_type.y new file mode 100644 index 0000000..3f5084a --- /dev/null +++ b/vsql/std_6_1_data_type.y @@ -0,0 +1,121 @@ +%% + +data_type: + predefined_type { $$.v = $1.v as Type } + +predefined_type: + character_string_type { $$.v = $1.v as Type } +| numeric_type { $$.v = $1.v as Type } +| boolean_type { $$.v = $1.v as Type } +| datetime_type { $$.v = $1.v as Type } + +character_string_type: + CHARACTER { $$.v = new_type('CHARACTER', 1, 0) } +| CHARACTER left_paren character_length right_paren { + $$.v = new_type('CHARACTER', ($3.v as string).int(), 0) + } +| CHAR { $$.v = new_type('CHARACTER', 1, 0) } +| CHAR left_paren character_length right_paren { + $$.v = new_type('CHARACTER', ($3.v as string).int(), 0) + } +| CHARACTER VARYING left_paren character_length right_paren { + $$.v = new_type('CHARACTER VARYING', ($4.v as string).int(), 0) + } +| CHAR VARYING left_paren character_length right_paren { + $$.v = new_type('CHARACTER VARYING', ($4.v as string).int(), 0) + } +| VARCHAR left_paren character_length right_paren { + $$.v = new_type('CHARACTER VARYING', ($3.v as string).int(), 0) + } + +numeric_type: + exact_numeric_type { $$.v = $1.v as Type } +| approximate_numeric_type { $$.v = $1.v as Type } + +exact_numeric_type: + NUMERIC { $$.v = new_type('NUMERIC', 0, 0) } +| NUMERIC left_paren precision right_paren { + $$.v = new_type('NUMERIC', ($3.v as string).int(), 0) + } +| NUMERIC left_paren precision comma scale right_paren { + $$.v = new_type('NUMERIC', ($3.v as string).int(), ($5.v as string).i16()) + } +| DECIMAL { $$.v = new_type('DECIMAL', 0, 0) } +| DECIMAL left_paren precision right_paren { + $$.v = new_type('DECIMAL', ($3.v as string).int(), 0) + } +| DECIMAL left_paren precision comma scale right_paren { + $$.v = new_type('DECIMAL', ($3.v as string).int(), ($5.v as string).i16()) + } +| SMALLINT { $$.v = new_type('SMALLINT', 0, 0) } +| INTEGER { $$.v = new_type('INTEGER', 0, 0) } +| INT { $$.v = new_type('INTEGER', 0, 0) } +| BIGINT { $$.v = new_type('BIGINT', 0, 0) } + +approximate_numeric_type: + FLOAT { $$.v = new_type('FLOAT', 0, 0) } +| FLOAT left_paren precision right_paren { + $$.v = new_type('FLOAT', ($3.v as string).int(), 0) + } +| REAL { $$.v = new_type('REAL', 0, 0) } +| DOUBLE PRECISION { $$.v = new_type('DOUBLE PRECISION', 0, 0) } + +length: + unsigned_integer { $$.v = $1.v as string } + +character_length: + length { $$.v = $1.v as string } + +char_length_units: + CHARACTERS { $$.v = $1.v as string } +| OCTETS { $$.v = $1.v as string } + +precision: + unsigned_integer { $$.v = $1.v as string } + +scale: + unsigned_integer { $$.v = $1.v as string } + +boolean_type: + BOOLEAN { $$.v = new_type('BOOLEAN', 0, 0) } + +datetime_type: + DATE { $$.v = new_type('DATE', 0, 0) } +| TIME { $$.v = parse_time_prec_tz_type('0', false)! } +| TIME left_paren time_precision right_paren { + $$.v = parse_time_prec_tz_type($3.v as string, false)! + } +| TIME with_or_without_time_zone { + $$.v = parse_time_prec_tz_type('0', $2.v as bool)! + } +| TIME left_paren time_precision right_paren with_or_without_time_zone { + $$.v = parse_time_prec_tz_type($3.v as string, $5.v as bool)! + } +| TIMESTAMP { $$.v = parse_timestamp_prec_tz_type('0', false)! } +| TIMESTAMP left_paren timestamp_precision right_paren { + $$.v = parse_timestamp_prec_tz_type($3.v as string, false)! + } +| TIMESTAMP with_or_without_time_zone { + // ISO/IEC 9075-2:2016(E), 6.1, 36) If is not + // specified, then 6 is implicit. + $$.v = parse_timestamp_prec_tz_type('6', $2.v as bool)! +} +| TIMESTAMP left_paren timestamp_precision right_paren + with_or_without_time_zone { + $$.v = parse_timestamp_prec_tz_type($3.v as string, $5.v as bool)! + } + +with_or_without_time_zone: + WITH TIME ZONE { $$.v = true } +| WITHOUT TIME ZONE { $$.v = false } + +time_precision: + time_fractional_seconds_precision { $$.v = $1.v as string } + +timestamp_precision: + time_fractional_seconds_precision { $$.v = $1.v as string } + +time_fractional_seconds_precision: + unsigned_integer { $$.v = $1.v as string } + +%% diff --git a/vsql/std_value_expression.v b/vsql/std_6_28_value_expression.v similarity index 67% rename from vsql/std_value_expression.v rename to vsql/std_6_28_value_expression.v index 363852c..5d35681 100644 --- a/vsql/std_value_expression.v +++ b/vsql/std_6_28_value_expression.v @@ -2,20 +2,7 @@ module vsql // ISO/IEC 9075-2:2016(E), 6.28, // -// # Function -// // Specify a value. -// -// # Format -//~ -//~ /* ValueExpression */ ::= -//~ -> ValueExpression -//~ | -> ValueExpression -//~ -//~ /* CommonValueExpression */ ::= -//~ -> CommonValueExpression -//~ | -> CommonValueExpression -//~ | -> CommonValueExpression type ValueExpression = BooleanValueExpression | CommonValueExpression diff --git a/vsql/std_6_28_value_expression.y b/vsql/std_6_28_value_expression.y new file mode 100644 index 0000000..de37342 --- /dev/null +++ b/vsql/std_6_28_value_expression.y @@ -0,0 +1,22 @@ +%% + +value_expression: + common_value_expression { + $$.v = ValueExpression($1.v as CommonValueExpression) + } +| boolean_value_expression { + $$.v = ValueExpression($1.v as BooleanValueExpression) + } + +common_value_expression: + numeric_value_expression { + $$.v = CommonValueExpression($1.v as NumericValueExpression) + } +| string_value_expression { + $$.v = CommonValueExpression($1.v as CharacterValueExpression) + } +| datetime_value_expression { + $$.v = CommonValueExpression($1.v as DatetimePrimary) + } + +%% diff --git a/vsql/std_numeric_value_expression.v b/vsql/std_6_29_numeric_value_expression.v similarity index 77% rename from vsql/std_numeric_value_expression.v rename to vsql/std_6_29_numeric_value_expression.v index 80cd83a..914cf52 100644 --- a/vsql/std_numeric_value_expression.v +++ b/vsql/std_6_29_numeric_value_expression.v @@ -2,29 +2,7 @@ module vsql // ISO/IEC 9075-2:2016(E), 6.29, // -// # Function -// // Specify a numeric value. -// -// # Format -//~ -//~ /* NumericValueExpression */ ::= -//~ -> numeric_value_expression_1 -//~ | -> numeric_value_expression_2 -//~ | -> numeric_value_expression_2 -//~ -//~ /* Term */ ::= -//~ -> term_1 -//~ | -> term_2 -//~ | -> term_2 -//~ -//~ /* NumericPrimary */ ::= -//~ -//~ | -> factor_2 -//~ -//~ /* NumericPrimary */ ::= -//~ -> NumericPrimary -//~ | -> NumericPrimary struct NumericValueExpression { n ?&NumericValueExpression @@ -56,7 +34,7 @@ fn (e NumericValueExpression) compile(mut c Compiler) !CompileResult { // TODO(elliotchance): This is not correct, we would have to return // the highest resolution type (need to check the SQL standard about // this behavior). - typ: compiled_n.typ + typ: compiled_n.typ contains_agg: compiled_term.contains_agg || compiled_n.contains_agg } } @@ -85,13 +63,13 @@ fn (e Term) compile(mut c Compiler) !CompileResult { compiled_term := term.compile(mut c)! return CompileResult{ - run: fn [e, compiled_term, compiled_factor] (mut conn Connection, data Row, params map[string]Value) !Value { + run: fn [e, compiled_term, compiled_factor] (mut conn Connection, data Row, params map[string]Value) !Value { mut left := compiled_term.run(mut conn, data, params)! mut right := compiled_factor.run(mut conn, data, params)! return eval_binary(mut conn, data, left, e.op, right, params)! } - typ: compiled_term.typ + typ: compiled_term.typ contains_agg: compiled_factor.contains_agg || compiled_term.contains_agg } } @@ -112,7 +90,7 @@ fn (e SignedValueExpressionPrimary) compile(mut c Compiler) !CompileResult { compiled := e.e.compile(mut c)! return CompileResult{ - run: fn [e, compiled] (mut conn Connection, data Row, params map[string]Value) !Value { + run: fn [e, compiled] (mut conn Connection, data Row, params map[string]Value) !Value { value := compiled.run(mut conn, data, params)! key := '${e.sign} ${value.typ.typ}' @@ -123,7 +101,7 @@ fn (e SignedValueExpressionPrimary) compile(mut c Compiler) !CompileResult { return sqlstate_42883('operator does not exist: ${key}') } - typ: compiled.typ + typ: compiled.typ contains_agg: compiled.contains_agg } } @@ -194,22 +172,6 @@ fn parse_factor_2(sign string, expr NumericPrimary) !NumericPrimary { return SignedValueExpressionPrimary{sign, expr} } -fn parse_term_1(factor NumericPrimary) !Term { - return Term{none, '', factor} -} - -fn parse_term_2(term Term, op string, factor NumericPrimary) !Term { - return Term{&term, op, factor} -} - -fn parse_numeric_value_expression_1(term Term) !NumericValueExpression { - return NumericValueExpression{none, '', term} -} - -fn parse_numeric_value_expression_2(n NumericValueExpression, op string, term Term) !NumericValueExpression { - return NumericValueExpression{&n, op, term} -} - fn eval_binary(mut conn Connection, data Row, x Value, op string, y Value, params map[string]Value) !Value { mut left := x mut right := y diff --git a/vsql/std_6_29_numeric_value_expression.y b/vsql/std_6_29_numeric_value_expression.y new file mode 100644 index 0000000..d0ada0d --- /dev/null +++ b/vsql/std_6_29_numeric_value_expression.y @@ -0,0 +1,37 @@ +%% + +numeric_value_expression: + term { $$.v = NumericValueExpression{term: $1.v as Term} } +| numeric_value_expression plus_sign term { + n := $1.v as NumericValueExpression + $$.v = NumericValueExpression{&n, '+', $3.v as Term} + } +| numeric_value_expression minus_sign term { + n := $1.v as NumericValueExpression + $$.v = NumericValueExpression{&n, '-', $3.v as Term} + } + +term: + factor { $$.v = Term{factor: $1.v as NumericPrimary} } +| term asterisk factor { + t := $1.v as Term + $$.v = Term{&t, '*', $3.v as NumericPrimary} + } +| term solidus factor { + t := $1.v as Term + $$.v = Term{&t, '/', $3.v as NumericPrimary} + } + +factor: + numeric_primary { $$.v = $1.v as NumericPrimary } +| sign numeric_primary { + $$.v = parse_factor_2($1.v as string, $2.v as NumericPrimary)! + } + +numeric_primary: + value_expression_primary { + $$.v = NumericPrimary($1.v as ValueExpressionPrimary) + } +| numeric_value_function { $$.v = NumericPrimary($1.v as RoutineInvocation) } + +%% diff --git a/vsql/std_6_30_numeric_value_function.v b/vsql/std_6_30_numeric_value_function.v new file mode 100644 index 0000000..d212cc3 --- /dev/null +++ b/vsql/std_6_30_numeric_value_function.v @@ -0,0 +1,172 @@ +module vsql + +import math + +// ISO/IEC 9075-2:2016(E), 6.30, +// +// Specify a function yielding a value of type numeric. + +// POSITION(CHARACTER VARYING IN CHARACTER VARYING) INTEGER +fn func_position(args []Value) !Value { + index := args[1].string_value().index(args[0].string_value()) or { -1 } + + return new_integer_value(index + 1) +} + +// CHAR_LENGTH(CHARACTER VARYING) INTEGER +fn func_char_length(args []Value) !Value { + return new_integer_value(args[0].string_value().runes().len) +} + +// OCTET_LENGTH(CHARACTER VARYING) INTEGER +fn func_octet_length(args []Value) !Value { + return new_integer_value(args[0].string_value().len) +} + +// UPPER(CHARACTER VARYING) CHARACTER VARYING +fn func_upper(args []Value) !Value { + return new_varchar_value(args[0].string_value().to_upper()) +} + +// LOWER(CHARACTER VARYING) CHARACTER VARYING +fn func_lower(args []Value) !Value { + return new_varchar_value(args[0].string_value().to_lower()) +} + +// ABS(DOUBLE PRECISION) DOUBLE PRECISION +fn func_abs(args []Value) !Value { + return new_double_precision_value(math.abs(args[0].f64_value())) +} + +// ABS(NUMERIC) NUMERIC +fn func_abs_numeric(args []Value) !Value { + n := args[0].numeric_value() + if n.is_negative() { + return new_numeric_value_from_numeric(n.neg()) + } + + return args[0] +} + +// SIN(DOUBLE PRECISION) DOUBLE PRECISION +fn func_sin(args []Value) !Value { + return new_double_precision_value(math.sin(args[0].f64_value())) +} + +// COS(DOUBLE PRECISION) DOUBLE PRECISION +fn func_cos(args []Value) !Value { + return new_double_precision_value(math.cos(args[0].f64_value())) +} + +// TAN(DOUBLE PRECISION) DOUBLE PRECISION +fn func_tan(args []Value) !Value { + return new_double_precision_value(math.tan(args[0].f64_value())) +} + +// SINH(DOUBLE PRECISION) DOUBLE PRECISION +fn func_sinh(args []Value) !Value { + return new_double_precision_value(math.sinh(args[0].f64_value())) +} + +// COSH(DOUBLE PRECISION) DOUBLE PRECISION +fn func_cosh(args []Value) !Value { + return new_double_precision_value(math.cosh(args[0].f64_value())) +} + +// TANH(DOUBLE PRECISION) DOUBLE PRECISION +fn func_tanh(args []Value) !Value { + return new_double_precision_value(math.tanh(args[0].f64_value())) +} + +// ASIN(DOUBLE PRECISION) DOUBLE PRECISION +fn func_asin(args []Value) !Value { + return new_double_precision_value(math.asin(args[0].f64_value())) +} + +// ACOS(DOUBLE PRECISION) DOUBLE PRECISION +fn func_acos(args []Value) !Value { + return new_double_precision_value(math.acos(args[0].f64_value())) +} + +// ATAN(DOUBLE PRECISION) DOUBLE PRECISION +fn func_atan(args []Value) !Value { + return new_double_precision_value(math.atan(args[0].f64_value())) +} + +// MOD(DOUBLE PRECISION, DOUBLE PRECISION) DOUBLE PRECISION +fn func_mod(args []Value) !Value { + return new_double_precision_value(math.fmod(args[0].f64_value(), args[1].f64_value())) +} + +// MOD(NUMERIC, NUMERIC) NUMERIC +fn func_mod_numeric(args []Value) !Value { + value := args[0].numeric_value() + modulus := args[1].numeric_value() + + return new_numeric_value_from_numeric(value.subtract(value.divide(modulus)!.trunc().multiply(modulus))) +} + +// LOG(DOUBLE PRECISION) DOUBLE PRECISION +fn func_log(args []Value) !Value { + return new_double_precision_value(math.log2(args[0].f64_value())) +} + +// LOG10(DOUBLE PRECISION) DOUBLE PRECISION +fn func_log10(args []Value) !Value { + return new_double_precision_value(math.log10(args[0].f64_value())) +} + +// LN(DOUBLE PRECISION) DOUBLE PRECISION +fn func_ln(args []Value) !Value { + return new_double_precision_value(math.log(args[0].f64_value())) +} + +// EXP(DOUBLE PRECISION) DOUBLE PRECISION +fn func_exp(args []Value) !Value { + return new_double_precision_value(math.exp(args[0].f64_value())) +} + +// SQRT(DOUBLE PRECISION) DOUBLE PRECISION +fn func_sqrt(args []Value) !Value { + return new_double_precision_value(math.sqrt(args[0].f64_value())) +} + +// POWER(DOUBLE PRECISION, DOUBLE PRECISION) DOUBLE PRECISION +fn func_power(args []Value) !Value { + return new_double_precision_value(math.pow(args[0].f64_value(), args[1].f64_value())) +} + +// FLOOR(DOUBLE PRECISION) DOUBLE PRECISION +fn func_floor(args []Value) !Value { + return new_double_precision_value(math.floor(args[0].f64_value())) +} + +// FLOOR(NUMERIC) NUMERIC +fn func_floor_numeric(args []Value) !Value { + n := args[0].numeric_value() + if n.is_negative() { + return new_numeric_value_from_numeric(n.subtract(new_numeric_from_string('1')).trunc()) + } + + return new_numeric_value_from_numeric(n.trunc()) +} + +// CEIL(DOUBLE PRECISION) DOUBLE PRECISION +fn func_ceil(args []Value) !Value { + return new_double_precision_value(math.ceil(args[0].f64_value())) +} + +// CEIL(NUMERIC) NUMERIC +fn func_ceil_numeric(args []Value) !Value { + n := args[0].numeric_value() + t := n.trunc() + if n.equals(t) { + return args[0] + } + + if n.is_negative() { + return new_numeric_value_from_numeric(t) + } + + return new_numeric_value_from_numeric(t.add(new_numeric_from_string('1'))) +} diff --git a/vsql/std_6_30_numeric_value_function.y b/vsql/std_6_30_numeric_value_function.y new file mode 100644 index 0000000..f61ca38 --- /dev/null +++ b/vsql/std_6_30_numeric_value_function.y @@ -0,0 +1,160 @@ +%% + +numeric_value_function: + position_expression { $$.v = $1.v as RoutineInvocation } +| length_expression { $$.v = $1.v as RoutineInvocation } +| absolute_value_expression { $$.v = $1.v as RoutineInvocation } +| modulus_expression { $$.v = $1.v as RoutineInvocation } +| trigonometric_function { $$.v = $1.v as RoutineInvocation } +| common_logarithm { $$.v = $1.v as RoutineInvocation } +| natural_logarithm { $$.v = $1.v as RoutineInvocation } +| exponential_function { $$.v = $1.v as RoutineInvocation } +| power_function { $$.v = $1.v as RoutineInvocation } +| square_root { $$.v = $1.v as RoutineInvocation } +| floor_function { $$.v = $1.v as RoutineInvocation } +| ceiling_function { $$.v = $1.v as RoutineInvocation } + +position_expression: + character_position_expression { $$.v = $1.v as RoutineInvocation } + +character_position_expression: + POSITION left_paren character_value_expression_1 IN + character_value_expression_2 right_paren { + $$.v = RoutineInvocation{'POSITION', [ + ValueExpression(CommonValueExpression($3.v as CharacterValueExpression)), + ValueExpression(CommonValueExpression($5.v as CharacterValueExpression)), + ]} + } + +character_value_expression_1: + character_value_expression { $$.v = $1.v as CharacterValueExpression } + +character_value_expression_2: + character_value_expression { $$.v = $1.v as CharacterValueExpression } + +length_expression: + char_length_expression { $$.v = $1.v as RoutineInvocation } +| octet_length_expression { $$.v = $1.v as RoutineInvocation } + +char_length_expression: + CHAR_LENGTH left_paren character_value_expression right_paren { + $$.v = RoutineInvocation{'CHAR_LENGTH', [ + ValueExpression(CommonValueExpression($3.v as CharacterValueExpression)), + ]} + } +| CHARACTER_LENGTH left_paren character_value_expression right_paren { + $$.v = RoutineInvocation{'CHAR_LENGTH', [ + ValueExpression(CommonValueExpression($3.v as CharacterValueExpression)), + ]} + } + +octet_length_expression: + OCTET_LENGTH left_paren string_value_expression right_paren { + $$.v = RoutineInvocation{'OCTET_LENGTH', [ + ValueExpression(CommonValueExpression($3.v as CharacterValueExpression)), + ]} + } + +absolute_value_expression: + ABS left_paren numeric_value_expression right_paren { + $$.v = RoutineInvocation{'ABS', [ + ValueExpression(CommonValueExpression($3.v as NumericValueExpression)) + ]} + } + +modulus_expression: + MOD left_paren numeric_value_expression_dividend comma + numeric_value_expression_divisor right_paren { + $$.v = RoutineInvocation{'MOD', [ + ValueExpression(CommonValueExpression($3.v as NumericValueExpression)) + ValueExpression(CommonValueExpression($5.v as NumericValueExpression)) + ]} + } + +numeric_value_expression_dividend: + numeric_value_expression { $$.v = $1.v as NumericValueExpression } + +numeric_value_expression_divisor: + numeric_value_expression { $$.v = $1.v as NumericValueExpression } + +trigonometric_function: + trigonometric_function_name left_paren numeric_value_expression right_paren { + $$.v = RoutineInvocation{$1.v as string, [ + ValueExpression(CommonValueExpression($3.v as NumericValueExpression)), + ]} + } + +trigonometric_function_name: + SIN { $$.v = $1.v as string } +| COS { $$.v = $1.v as string } +| TAN { $$.v = $1.v as string } +| SINH { $$.v = $1.v as string } +| COSH { $$.v = $1.v as string } +| TANH { $$.v = $1.v as string } +| ASIN { $$.v = $1.v as string } +| ACOS { $$.v = $1.v as string } +| ATAN { $$.v = $1.v as string } + +common_logarithm: + LOG10 left_paren numeric_value_expression right_paren { + $$.v = RoutineInvocation{'LOG10', [ + ValueExpression(CommonValueExpression($3.v as NumericValueExpression)) + ]} + } + +natural_logarithm: + LN left_paren numeric_value_expression right_paren { + $$.v = RoutineInvocation{'LN', [ + ValueExpression(CommonValueExpression($3.v as NumericValueExpression)) + ]} + } + +exponential_function: + EXP left_paren numeric_value_expression right_paren { + $$.v = RoutineInvocation{'EXP', [ + ValueExpression(CommonValueExpression($3.v as NumericValueExpression)) + ]} + } + +power_function: + POWER left_paren numeric_value_expression_base comma + numeric_value_expression_exponent right_paren { + $$.v = RoutineInvocation{'POWER', [ + ValueExpression(CommonValueExpression($3.v as NumericValueExpression)), + ValueExpression(CommonValueExpression($5.v as NumericValueExpression)) + ]} + } + +numeric_value_expression_base: + numeric_value_expression { $$.v = $1.v as NumericValueExpression } + +numeric_value_expression_exponent: + numeric_value_expression { $$.v = $1.v as NumericValueExpression } + +square_root: + SQRT left_paren numeric_value_expression right_paren { + $$.v = RoutineInvocation{'SQRT', [ + ValueExpression(CommonValueExpression($3.v as NumericValueExpression)) + ]} + } + +floor_function: + FLOOR left_paren numeric_value_expression right_paren { + $$.v = RoutineInvocation{'FLOOR', [ + ValueExpression(CommonValueExpression($3.v as NumericValueExpression)) + ]} + } + +ceiling_function: + CEIL left_paren numeric_value_expression right_paren { + $$.v = RoutineInvocation{'CEILING', [ + ValueExpression(CommonValueExpression($3.v as NumericValueExpression)) + ]} + } +| CEILING left_paren numeric_value_expression right_paren { + $$.v = RoutineInvocation{'CEILING', [ + ValueExpression(CommonValueExpression($3.v as NumericValueExpression)) + ]} + } + +%% diff --git a/vsql/std_string_value_expression.v b/vsql/std_6_31_string_value_expression.v similarity index 63% rename from vsql/std_string_value_expression.v rename to vsql/std_6_31_string_value_expression.v index 5e23839..013108c 100644 --- a/vsql/std_string_value_expression.v +++ b/vsql/std_6_31_string_value_expression.v @@ -2,30 +2,7 @@ module vsql // ISO/IEC 9075-2:2016(E), 6.31, // -// # Function -// // Specify a character string value or a binary string value. -// -// # Format -//~ -//~ /* CharacterValueExpression */ ::= -//~ -//~ -//~ /* CharacterValueExpression */ ::= -//~ -> CharacterValueExpression -//~ | -> CharacterValueExpression -//~ -//~ /* Concatenation */ ::= -//~ -//~ -//~ -> concatenation -//~ -//~ /* CharacterPrimary */ ::= -//~ -//~ -//~ /* CharacterPrimary */ ::= -//~ -> CharacterPrimary -//~ | -> CharacterPrimary type CharacterValueExpression = CharacterPrimary | Concatenation @@ -77,7 +54,7 @@ fn (e Concatenation) compile(mut c Compiler) !CompileResult { compiled_right := e.right.compile(mut c)! return CompileResult{ - run: fn [compiled_left, compiled_right] (mut conn Connection, data Row, params map[string]Value) !Value { + run: fn [compiled_left, compiled_right] (mut conn Connection, data Row, params map[string]Value) !Value { mut left := compiled_left.run(mut conn, data, params)! mut right := compiled_right.run(mut conn, data, params)! @@ -88,11 +65,7 @@ fn (e Concatenation) compile(mut c Compiler) !CompileResult { return sqlstate_42883('operator does not exist: ${left.typ.typ} || ${right.typ.typ}') } - typ: new_type('CHARACTER VARYING', 0, 0) + typ: new_type('CHARACTER VARYING', 0, 0) contains_agg: compiled_left.contains_agg || compiled_right.contains_agg } } - -fn parse_concatenation(a CharacterValueExpression, b CharacterValueExpression) !Concatenation { - return Concatenation{a, b} -} diff --git a/vsql/std_6_31_string_value_expression.y b/vsql/std_6_31_string_value_expression.y new file mode 100644 index 0000000..ed05cf6 --- /dev/null +++ b/vsql/std_6_31_string_value_expression.y @@ -0,0 +1,29 @@ +%% + +string_value_expression: + character_value_expression { $$.v = $1.v as CharacterValueExpression } + +character_value_expression: + concatenation { $$.v = CharacterValueExpression($1.v as Concatenation) } +| character_factor { $$.v = CharacterValueExpression($1.v as CharacterPrimary) } + +concatenation: + character_value_expression concatenation_operator character_factor { + $$.v = Concatenation{ + $1.v as CharacterValueExpression + $3.v as CharacterPrimary + } + } + +character_factor: + character_primary { $$.v = $1.v as CharacterPrimary } + +character_primary: + value_expression_primary { + $$.v = CharacterPrimary($1.v as ValueExpressionPrimary) + } +| string_value_function { + $$.v = CharacterPrimary($1.v as CharacterValueFunction) + } + +%% diff --git a/vsql/std_6_32_string_value_function.v b/vsql/std_6_32_string_value_function.v new file mode 100644 index 0000000..5a45ca3 --- /dev/null +++ b/vsql/std_6_32_string_value_function.v @@ -0,0 +1,144 @@ +module vsql + +// ISO/IEC 9075-2:2016(E), 6.32, +// +// Specify a function yielding a value of type character string or binary +// string. + +type CharacterValueFunction = CharacterSubstringFunction + | RoutineInvocation // + | TrimFunction + +fn (e CharacterValueFunction) pstr(params map[string]Value) string { + return match e { + RoutineInvocation, CharacterSubstringFunction, TrimFunction { + e.pstr(params) + } + } +} + +fn (e CharacterValueFunction) compile(mut c Compiler) !CompileResult { + match e { + CharacterSubstringFunction, TrimFunction, RoutineInvocation { + return e.compile(mut c)! + } + } +} + +struct CharacterSubstringFunction { + value CharacterValueExpression + from ?NumericValueExpression + @for ?NumericValueExpression + using string // CHARACTERS or OCTETS or '' +} + +fn (e CharacterSubstringFunction) pstr(params map[string]Value) string { + mut s := 'SUBSTRING(${e.value.pstr(params)}' + + if from := e.from { + s += ' FROM ${from.pstr(params)}' + } + + if @for := e.@for { + s += ' FOR ${@for.pstr(params)}' + } + + return s + ' USING ${e.using})' +} + +fn (e CharacterSubstringFunction) compile(mut c Compiler) !CompileResult { + compiled_value := e.value.compile(mut c)! + compiled_from := if f := e.from { + f.compile(mut c)! + } else { + CompileResult{ + run: unsafe { nil } + typ: Type{} + contains_agg: false + } + } + compiled_for := if f := e.@for { + f.compile(mut c)! + } else { + CompileResult{ + run: unsafe { nil } + typ: Type{} + contains_agg: false + } + } + + return CompileResult{ + run: fn [e, compiled_value, compiled_from, compiled_for] (mut conn Connection, data Row, params map[string]Value) !Value { + value := compiled_value.run(mut conn, data, params)! + + mut from := 0 + if e.from != none { + from = int((compiled_from.run(mut conn, data, params)!).as_int() - 1) + } + + if e.using == 'CHARACTERS' { + characters := value.string_value().runes() + + if from >= characters.len || from < 0 { + return new_varchar_value('') + } + + mut @for := characters.len - from + if e.@for != none { + @for = int((compiled_for.run(mut conn, data, params)!).as_int()) + } + + return new_varchar_value(characters[from..from + @for].string()) + } + + if from >= value.string_value().len || from < 0 { + return new_varchar_value('') + } + + mut @for := value.string_value().len - from + if e.@for != none { + @for = int((compiled_for.run(mut conn, data, params)!).as_int()) + } + + return new_varchar_value(value.string_value().substr(from, from + @for)) + } + typ: new_type('CHARACTER VARYING', 0, 0) + contains_agg: compiled_value.contains_agg + } +} + +struct TrimFunction { + // LEADING, TRAILING or BOTH + specification string + // When not provided, it will default to ' '. + character CharacterValueExpression + source CharacterValueExpression +} + +fn (e TrimFunction) pstr(params map[string]Value) string { + return 'TRIM(${e.specification} ${e.character.pstr(params)} FROM ${e.source.pstr(params)})' +} + +fn (e TrimFunction) compile(mut c Compiler) !CompileResult { + compiled_source := e.source.compile(mut c)! + compiled_character := e.character.compile(mut c)! + + return CompileResult{ + run: fn [e, compiled_source, compiled_character] (mut conn Connection, data Row, params map[string]Value) !Value { + source := compiled_source.run(mut conn, data, params)! + character := compiled_character.run(mut conn, data, params)! + + if e.specification == 'LEADING' { + return new_varchar_value(source.string_value().trim_left(character.string_value())) + } + + if e.specification == 'TRAILING' { + return new_varchar_value(source.string_value().trim_right(character.string_value())) + } + + return new_varchar_value(source.string_value().trim(character.string_value())) + } + typ: new_type('CHARACTER VARYING', 0, 0) + contains_agg: compiled_source.contains_agg || compiled_character.contains_agg + } +} diff --git a/vsql/std_6_32_string_value_function.y b/vsql/std_6_32_string_value_function.y new file mode 100644 index 0000000..28104dc --- /dev/null +++ b/vsql/std_6_32_string_value_function.y @@ -0,0 +1,87 @@ +%% + +string_value_function: + character_value_function { $$.v = $1.v as CharacterValueFunction } + +character_value_function: + character_substring_function { + $$.v = CharacterValueFunction($1.v as CharacterSubstringFunction) + } +| fold { $$.v = CharacterValueFunction($1.v as RoutineInvocation) } +| trim_function { $$.v = CharacterValueFunction($1.v as TrimFunction) } + +character_substring_function: + SUBSTRING left_paren character_value_expression FROM start_position + right_paren { + $$.v = CharacterSubstringFunction{$3.v as CharacterValueExpression, + $5.v as NumericValueExpression, none, 'CHARACTERS'} + } +| SUBSTRING left_paren character_value_expression FROM start_position FOR + string_length right_paren { + $$.v = CharacterSubstringFunction{$3.v as CharacterValueExpression, + $5.v as NumericValueExpression, $7.v as NumericValueExpression, + 'CHARACTERS'} + } +| SUBSTRING left_paren character_value_expression FROM start_position USING + char_length_units right_paren { + $$.v = CharacterSubstringFunction{$3.v as CharacterValueExpression, + $5.v as NumericValueExpression, none, $7.v as string} + } +| SUBSTRING left_paren character_value_expression FROM start_position FOR + string_length USING char_length_units right_paren { + $$.v = CharacterSubstringFunction{$3.v as CharacterValueExpression, + $5.v as NumericValueExpression, $7.v as NumericValueExpression, + $9.v as string} + } + +fold: + UPPER left_paren character_value_expression right_paren { + $$.v = RoutineInvocation{'UPPER', [ + ValueExpression(CommonValueExpression($3.v as CharacterValueExpression))]} + } +| LOWER left_paren character_value_expression right_paren { + $$.v = RoutineInvocation{'LOWER', [ + ValueExpression(CommonValueExpression($3.v as CharacterValueExpression))]} + } + +trim_function: + TRIM left_paren trim_operands right_paren { $$.v = $3.v as TrimFunction } + +trim_operands: + trim_source { + space := CharacterValueExpression(CharacterPrimary(ValueExpressionPrimary(NonparenthesizedValueExpressionPrimary(ValueSpecification(new_varchar_value(' ')))))) + $$.v = TrimFunction{'BOTH', space, $1.v as CharacterValueExpression} + } +| FROM trim_source { + space := CharacterValueExpression(CharacterPrimary(ValueExpressionPrimary(NonparenthesizedValueExpressionPrimary(ValueSpecification(new_varchar_value(' ')))))) + $$.v = TrimFunction{'BOTH', space, $2.v as CharacterValueExpression} + } +| trim_specification FROM trim_source { + space := CharacterValueExpression(CharacterPrimary(ValueExpressionPrimary(NonparenthesizedValueExpressionPrimary(ValueSpecification(new_varchar_value(' ')))))) + $$.v = TrimFunction{$1.v as string, space, $3.v as CharacterValueExpression} + } +| trim_character FROM trim_source { + $$.v = TrimFunction{'BOTH', $1.v as CharacterValueExpression, $3.v as CharacterValueExpression} + } +| trim_specification trim_character FROM trim_source { + $$.v = TrimFunction{$1.v as string, $2.v as CharacterValueExpression, $4.v as CharacterValueExpression} + } + +trim_source: + character_value_expression { $$.v = $1.v as CharacterValueExpression } + +trim_specification: + LEADING { $$.v = $1.v as string } +| TRAILING { $$.v = $1.v as string } +| BOTH { $$.v = $1.v as string } + +trim_character: + character_value_expression { $$.v = $1.v as CharacterValueExpression } + +start_position: + numeric_value_expression { $$.v = $1.v as NumericValueExpression } + +string_length: + numeric_value_expression { $$.v = $1.v as NumericValueExpression } + +%% diff --git a/vsql/std_datetime_value_expression.v b/vsql/std_6_35_datetime_value_expression.v similarity index 53% rename from vsql/std_datetime_value_expression.v rename to vsql/std_6_35_datetime_value_expression.v index 21e79c9..6dfda5e 100644 --- a/vsql/std_datetime_value_expression.v +++ b/vsql/std_6_35_datetime_value_expression.v @@ -2,24 +2,7 @@ module vsql // ISO/IEC 9075-2:2016(E), 6.35, // -// # Function -// // Specify a datetime value. -// -// # Format -//~ -//~ /* DatetimePrimary */ ::= -//~ -//~ -//~ /* DatetimePrimary */ ::= -//~ -//~ -//~ /* DatetimePrimary */ ::= -//~ -//~ -//~ /* DatetimePrimary */ ::= -//~ -> DatetimePrimary -//~ | -> DatetimePrimary type DatetimePrimary = DatetimeValueFunction | ValueExpressionPrimary diff --git a/vsql/std_6_35_datetime_value_expression.y b/vsql/std_6_35_datetime_value_expression.y new file mode 100644 index 0000000..122bf8f --- /dev/null +++ b/vsql/std_6_35_datetime_value_expression.y @@ -0,0 +1,14 @@ +%% + +datetime_value_expression: + datetime_term { $$.v = $1.v as DatetimePrimary } + +datetime_term: + datetime_factor { $$.v = $1.v as DatetimePrimary } + +datetime_factor: + datetime_primary { $$.v = $1.v as DatetimePrimary } + +// Note: datetime_primary is defined in grammar.y. See there for details. + +%% diff --git a/vsql/std_datetime_value_function.v b/vsql/std_6_36_datetime_value_function.v similarity index 52% rename from vsql/std_datetime_value_function.v rename to vsql/std_6_36_datetime_value_function.v index e3c656a..a7d2987 100644 --- a/vsql/std_datetime_value_function.v +++ b/vsql/std_6_36_datetime_value_function.v @@ -2,39 +2,7 @@ module vsql // ISO/IEC 9075-2:2016(E), 6.36, // -// # Function -// // Specify a function yielding a value of type datetime. -// -// # Format -//~ -//~ /* DatetimeValueFunction */ ::= -//~ -//~ | -//~ | -//~ | -//~ | -//~ -//~ /* DatetimeValueFunction */ ::= -//~ CURRENT_DATE -> current_date -//~ -//~ /* DatetimeValueFunction */ ::= -//~ CURRENT_TIME -> current_time_1 -//~ | CURRENT_TIME
    . + +struct DerivedColumn { + expr ValueExpression + as_clause Identifier // will be empty if not provided +} + +type SelectList = AsteriskExpr | QualifiedAsteriskExpr | []DerivedColumn + +type AsteriskExpr = bool + +struct QualifiedAsteriskExpr { + table_name Identifier +} + +fn (e QualifiedAsteriskExpr) str() string { + return '${e.table_name}.*' +} + +struct QuerySpecification { + exprs SelectList + table_expression TableExpression + offset ?ValueExpression + fetch ?ValueExpression +} diff --git a/vsql/std_7_16_query_specification.y b/vsql/std_7_16_query_specification.y new file mode 100644 index 0000000..3d48c27 --- /dev/null +++ b/vsql/std_7_16_query_specification.y @@ -0,0 +1,50 @@ +%% + +query_specification: + SELECT select_list table_expression { + $$.v = QuerySpecification{ + exprs: $2.v as SelectList + table_expression: $3.v as TableExpression + } + } + +select_list: + asterisk { $$.v = SelectList(AsteriskExpr(true)) } +| select_sublist { $$.v = $1.v as SelectList } +| select_list comma select_sublist { + mut new_select_list := (($1.v as SelectList) as []DerivedColumn).clone() + new_select_list << (($3.v as SelectList) as []DerivedColumn)[0] + $$.v = SelectList(new_select_list) + } + +select_sublist: + derived_column { $$.v = SelectList([$1.v as DerivedColumn]) } +| qualified_asterisk { $$.v = SelectList($1.v as QualifiedAsteriskExpr) } + +// was: asterisked_identifier_chain period asterisk +qualified_asterisk: + asterisked_identifier_chain OPERATOR_PERIOD_ASTERISK { + $$.v = QualifiedAsteriskExpr{ + new_column_identifier(($1.v as IdentifierChain).identifier)! + } + } + +asterisked_identifier_chain: + asterisked_identifier { $$.v = $1.v as IdentifierChain } + +asterisked_identifier: + identifier { $$.v = $1.v as IdentifierChain } + +derived_column: + value_expression { + $$.v = DerivedColumn{$1.v as ValueExpression, Identifier{}} + } +| value_expression as_clause { + $$.v = DerivedColumn{$1.v as ValueExpression, $2.v as Identifier} + } + +as_clause: + AS column_name { $$.v = $2.v as Identifier } +| column_name { $$.v = $1.v as Identifier } + +%% diff --git a/vsql/std_query_expression.v b/vsql/std_7_17_query_expression.v similarity index 81% rename from vsql/std_query_expression.v rename to vsql/std_7_17_query_expression.v index 4034d1e..e367855 100644 --- a/vsql/std_query_expression.v +++ b/vsql/std_7_17_query_expression.v @@ -4,69 +4,7 @@ import time // ISO/IEC 9075-2:2016(E), 7.17, // -// # Function -// // Specify a table. -// -// # Format -//~ -//~ /* QueryExpression */ ::= -//~ -> query_expression -//~ | -> query_expression_order -//~ | -//~ -> query_expression_offset -//~ | -//~ -> query_expression_order_offset -//~ | -//~ -> query_expression_fetch -//~ | -//~ -> query_expression_order_fetch -//~ | -//~ -//~ -> query_expression_order_offset_fetch -//~ | -//~ -//~ -> query_expression_offset_fetch -//~ -//~ /* SimpleTable */ ::= -//~ -//~ -//~ /* SimpleTable */ ::= -//~ -//~ -//~ /* SimpleTable */ ::= -//~ -//~ -//~ /* SimpleTable */ ::= -//~ -//~ |
    -//~ -//~ /* []SortSpecification */ ::= -//~ ORDER BY -> order_by -//~ -//~ /* ValueSpecification */ ::= -//~ OFFSET -> result_offset_clause -//~ -//~ /* ValueSpecification */ ::= -//~ FETCH FIRST -//~ -//~ -//~ ONLY -> fetch_first_clause -//~ -//~ /* ValueSpecification */ ::= -//~ -//~ -//~ /* ValueSpecification */ ::= -//~ -//~ -//~ /* ValueSpecification */ ::= -//~ -// -// These are non-standard, just to simplify standard rules: -//~ -//~ ::= -//~ ROW -//~ | ROWS struct QueryExpression { body SimpleTable @@ -87,36 +25,36 @@ fn parse_query_expression(body SimpleTable) !QueryExpression { fn parse_query_expression_order(body SimpleTable, order []SortSpecification) !QueryExpression { return QueryExpression{ - body: body + body: body order: order } } fn parse_query_expression_offset(body SimpleTable, offset ValueSpecification) !QueryExpression { return QueryExpression{ - body: body + body: body offset: offset } } fn parse_query_expression_order_offset(body SimpleTable, order []SortSpecification, offset ValueSpecification) !QueryExpression { return QueryExpression{ - body: body + body: body offset: offset - order: order + order: order } } fn parse_query_expression_fetch(body SimpleTable, fetch ValueSpecification) !QueryExpression { return QueryExpression{ - body: body + body: body fetch: fetch } } fn parse_query_expression_order_fetch(body SimpleTable, order []SortSpecification, fetch ValueSpecification) !QueryExpression { return QueryExpression{ - body: body + body: body fetch: fetch order: order } @@ -124,18 +62,18 @@ fn parse_query_expression_order_fetch(body SimpleTable, order []SortSpecificatio fn parse_query_expression_offset_fetch(body SimpleTable, offset ValueSpecification, fetch ValueSpecification) !QueryExpression { return QueryExpression{ - body: body + body: body offset: offset - fetch: fetch + fetch: fetch } } fn parse_query_expression_order_offset_fetch(body SimpleTable, order []SortSpecification, offset ValueSpecification, fetch ValueSpecification) !QueryExpression { return QueryExpression{ - body: body + body: body offset: offset - fetch: fetch - order: order + fetch: fetch + order: order } } @@ -231,7 +169,7 @@ fn (mut o OrderOperation) execute(rows []Row) ![]Row { head_cmp := row_cmp(mut o.conn, o.params, row, head.row, o.order)! if head_cmp < 0 { head = &RowLink{ - row: row + row: row next: head } continue @@ -244,7 +182,7 @@ fn (mut o OrderOperation) execute(rows []Row) ![]Row { cmp := row_cmp(mut o.conn, o.params, row, cursor.next.row, o.order)! if cmp < 0 { cursor.next = &RowLink{ - row: row + row: row next: cursor.next } inserted = true @@ -291,7 +229,7 @@ fn (l &RowLink) rows() []Row { fn row_cmp(mut conn Connection, params map[string]Value, r1 Row, r2 Row, specs []SortSpecification) !int { mut c := Compiler{ - conn: conn + conn: conn params: params } @@ -436,7 +374,7 @@ fn (o &LimitOperation) columns() Columns { fn (mut o LimitOperation) execute(rows []Row) ![]Row { mut c := Compiler{ - conn: o.conn + conn: o.conn params: o.params } mut offset := i64(0) diff --git a/vsql/std_7_17_query_expression.y b/vsql/std_7_17_query_expression.y new file mode 100644 index 0000000..45b442f --- /dev/null +++ b/vsql/std_7_17_query_expression.y @@ -0,0 +1,89 @@ +%% + +query_expression: + query_expression_body { $$.v = QueryExpression{body: $1.v as SimpleTable} } +| query_expression_body order_by_clause { + $$.v = QueryExpression{ + body: $1.v as SimpleTable + order: $2.v as []SortSpecification + } + } +| query_expression_body result_offset_clause { + $$.v = QueryExpression{ + body: $1.v as SimpleTable + offset: $2.v as ValueSpecification + } + } +| query_expression_body order_by_clause result_offset_clause { + $$.v = QueryExpression{ + body: $1.v as SimpleTable + offset: $3.v as ValueSpecification + order: $2.v as []SortSpecification + } + } +| query_expression_body fetch_first_clause { + $$.v = QueryExpression{ + body: $1.v as SimpleTable + fetch: $2.v as ValueSpecification + } + } +| query_expression_body order_by_clause fetch_first_clause { + $$.v = QueryExpression{ + body: $1.v as SimpleTable + fetch: $3.v as ValueSpecification + order: $2.v as []SortSpecification + } + } +| query_expression_body order_by_clause result_offset_clause + fetch_first_clause { + $$.v = QueryExpression{ + body: $1.v as SimpleTable + offset: $3.v as ValueSpecification + fetch: $4.v as ValueSpecification + order: $2.v as []SortSpecification + } + } +| query_expression_body result_offset_clause fetch_first_clause { + $$.v = QueryExpression{ + body: $1.v as SimpleTable + offset: $2.v as ValueSpecification + fetch: $3.v as ValueSpecification + } + } + +query_expression_body: + query_term { $$.v = $1.v as SimpleTable } + +query_term: + query_primary { $$.v = $1.v as SimpleTable } + +query_primary: + simple_table { $$.v = $1.v as SimpleTable } + +simple_table: + query_specification { $$.v = SimpleTable($1.v as QuerySpecification) } +| table_value_constructor { $$.v = $1.v as SimpleTable } + +order_by_clause: + ORDER BY sort_specification_list { $$.v = $3.v } + +result_offset_clause: + OFFSET offset_row_count row_or_rows { $$.v = $2.v } + +fetch_first_clause: + FETCH FIRST fetch_first_quantity row_or_rows ONLY { $$.v = $3.v } + +fetch_first_quantity: + fetch_first_row_count { $$.v = $1.v as ValueSpecification } + +offset_row_count: + simple_value_specification { $$.v = $1.v as ValueSpecification } + +fetch_first_row_count: + simple_value_specification { $$.v = $1.v as ValueSpecification } + +row_or_rows: + ROW +| ROWS + +%% diff --git a/vsql/std_7_19_subquery.v b/vsql/std_7_19_subquery.v new file mode 100644 index 0000000..a85107e --- /dev/null +++ b/vsql/std_7_19_subquery.v @@ -0,0 +1,7 @@ +module vsql + +// ISO/IEC 9075-2:2016(E), 7.19, +// +// Specify a scalar value, a row, or a table derived from a . + +type TablePrimaryBody = Identifier | QueryExpression diff --git a/vsql/std_7_19_subquery.y b/vsql/std_7_19_subquery.y new file mode 100644 index 0000000..5fc1be1 --- /dev/null +++ b/vsql/std_7_19_subquery.y @@ -0,0 +1,16 @@ +%% + +row_subquery: + subquery { $$.v = $1.v as QueryExpression } + +table_subquery: + subquery { $$.v = $1.v as TablePrimary } + +subquery: + left_paren query_expression right_paren { + $$.v = TablePrimary{ + body: $2.v as QueryExpression + } + } + +%% diff --git a/vsql/std_7_1_row_value_constructor.v b/vsql/std_7_1_row_value_constructor.v new file mode 100644 index 0000000..744e700 --- /dev/null +++ b/vsql/std_7_1_row_value_constructor.v @@ -0,0 +1,158 @@ +module vsql + +// ISO/IEC 9075-2:2016(E), 7.1, +// +// Specify a value or list of values to be constructed into a row. + +type ContextuallyTypedRowValueConstructor = BooleanValueExpression + | CommonValueExpression + | NullSpecification + | []ContextuallyTypedRowValueConstructorElement + +fn (e ContextuallyTypedRowValueConstructor) pstr(params map[string]Value) string { + return match e { + CommonValueExpression, BooleanValueExpression, NullSpecification { + e.pstr(params) + } + []ContextuallyTypedRowValueConstructorElement { + e.map(it.pstr(params)).join(', ') + } + } +} + +fn (e ContextuallyTypedRowValueConstructor) compile(mut c Compiler) !CompileResult { + match e { + CommonValueExpression, BooleanValueExpression, NullSpecification { + return e.compile(mut c)! + } + []ContextuallyTypedRowValueConstructorElement { + mut contains_agg := false + for element in e { + if element.compile(mut c)!.contains_agg { + contains_agg = true + } + } + + return e[0].compile(mut c)!.with_agg(contains_agg) + } + } +} + +type ContextuallyTypedRowValueConstructorElement = NullSpecification | ValueExpression + +fn (e ContextuallyTypedRowValueConstructorElement) pstr(params map[string]Value) string { + return match e { + ValueExpression, NullSpecification { + e.pstr(params) + } + } +} + +fn (e ContextuallyTypedRowValueConstructorElement) compile(mut c Compiler) !CompileResult { + match e { + ValueExpression, NullSpecification { + return e.compile(mut c)! + } + } +} + +type RowValueConstructorPredicand = BooleanPredicand | CommonValueExpression + +fn (e RowValueConstructorPredicand) pstr(params map[string]Value) string { + return match e { + CommonValueExpression, BooleanPredicand { + e.pstr(params) + } + } +} + +fn (e RowValueConstructorPredicand) compile(mut c Compiler) !CompileResult { + match e { + CommonValueExpression, BooleanPredicand { + return e.compile(mut c)! + } + } +} + +struct ExplicitRowValueConstructorRow { + exprs []ValueExpression +} + +fn (e ExplicitRowValueConstructorRow) pstr(params map[string]Value) string { + mut values := []string{} + for expr in e.exprs { + values << expr.pstr(params) + } + + return values.join(', ') +} + +type RowValueConstructor = BooleanValueExpression + | CommonValueExpression + | ExplicitRowValueConstructor + +fn (e RowValueConstructor) pstr(params map[string]Value) string { + return match e { + CommonValueExpression, BooleanValueExpression, ExplicitRowValueConstructor { + 'ROW(' + e.pstr(params) + ')' + } + } +} + +fn (e RowValueConstructor) compile(mut c Compiler) !CompileResult { + match e { + CommonValueExpression, BooleanValueExpression, ExplicitRowValueConstructor { + return e.compile(mut c)! + } + } +} + +fn (r RowValueConstructor) eval_row(mut conn Connection, data Row, params map[string]Value) !Row { + mut col_number := 1 + mut row := map[string]Value{} + mut c := Compiler{ + conn: conn + params: params + } + match r { + ExplicitRowValueConstructor { + match r { + ExplicitRowValueConstructorRow { + for expr in r.exprs { + row['COL${col_number}'] = expr.compile(mut c)!.run(mut conn, data, + params)! + col_number++ + } + } + QueryExpression { + panic('query expressions cannot be used in ROW constructors') + } + } + } + CommonValueExpression, BooleanValueExpression { + row['COL${col_number}'] = r.compile(mut c)!.run(mut conn, data, params)! + } + } + + return Row{ + data: row + } +} + +type ExplicitRowValueConstructor = ExplicitRowValueConstructorRow | QueryExpression + +fn (e ExplicitRowValueConstructor) pstr(params map[string]Value) string { + return match e { + ExplicitRowValueConstructorRow, QueryExpression { + e.pstr(params) + } + } +} + +fn (e ExplicitRowValueConstructor) compile(mut c Compiler) !CompileResult { + // ExplicitRowValueConstructorRow should never make it to eval because it will + // be reformatted into a ValuesOperation. + // + // QueryExpression will have already been resolved to a ValuesOperation. + return sqlstate_42601('missing or invalid expression provided') +} diff --git a/vsql/std_7_1_row_value_constructor.y b/vsql/std_7_1_row_value_constructor.y new file mode 100644 index 0000000..0edf728 --- /dev/null +++ b/vsql/std_7_1_row_value_constructor.y @@ -0,0 +1,75 @@ +%% + +row_value_constructor: + common_value_expression { + $$.v = RowValueConstructor($1.v as CommonValueExpression) + } +| boolean_value_expression { + $$.v = RowValueConstructor($1.v as BooleanValueExpression) + } +| explicit_row_value_constructor { + $$.v = RowValueConstructor($1.v as ExplicitRowValueConstructor) + } + +explicit_row_value_constructor: + ROW left_paren row_value_constructor_element_list right_paren { + $$.v = ExplicitRowValueConstructor(ExplicitRowValueConstructorRow{$3.v as []ValueExpression}) + } +| row_subquery { $$.v = ExplicitRowValueConstructor($1.v as QueryExpression) } + +row_value_constructor_element_list: + row_value_constructor_element { $$.v = [$1.v as ValueExpression] } +| row_value_constructor_element_list comma row_value_constructor_element { + $$.v = append_list($1.v as []ValueExpression, $3.v as ValueExpression) + } + +row_value_constructor_element: + value_expression { $$.v = $1.v as ValueExpression } + +contextually_typed_row_value_constructor: + common_value_expression { + $$.v = ContextuallyTypedRowValueConstructor($1.v as CommonValueExpression) + } +| boolean_value_expression { + $$.v = ContextuallyTypedRowValueConstructor($1.v as BooleanValueExpression) + } +| contextually_typed_value_specification { + $$.v = ContextuallyTypedRowValueConstructor($1.v as NullSpecification) + } +| left_paren contextually_typed_value_specification right_paren { + $$.v = ContextuallyTypedRowValueConstructor($2.v as NullSpecification) + } +| left_paren contextually_typed_row_value_constructor_element comma + contextually_typed_row_value_constructor_element_list right_paren { + $$.v = ContextuallyTypedRowValueConstructor(push_list( + $2.v as ContextuallyTypedRowValueConstructorElement, + $4.v as []ContextuallyTypedRowValueConstructorElement)) + } + +contextually_typed_row_value_constructor_element_list: + contextually_typed_row_value_constructor_element { + $$.v = [$1.v as ContextuallyTypedRowValueConstructorElement] + } +| contextually_typed_row_value_constructor_element_list comma + contextually_typed_row_value_constructor_element { + $$.v = append_list($1.v as []ContextuallyTypedRowValueConstructorElement, + $3.v as ContextuallyTypedRowValueConstructorElement) + } + +contextually_typed_row_value_constructor_element: + value_expression { + $$.v = ContextuallyTypedRowValueConstructorElement($1.v as ValueExpression) + } +| contextually_typed_value_specification { + $$.v = ContextuallyTypedRowValueConstructorElement($1.v as NullSpecification) + } + +row_value_constructor_predicand: + common_value_expression { + $$.v = RowValueConstructorPredicand($1.v as CommonValueExpression) + } +| boolean_predicand { + $$.v = RowValueConstructorPredicand($1.v as BooleanPredicand) + } + +%% diff --git a/vsql/std_7_2_row_value_expression.y b/vsql/std_7_2_row_value_expression.y new file mode 100644 index 0000000..e67f990 --- /dev/null +++ b/vsql/std_7_2_row_value_expression.y @@ -0,0 +1,20 @@ +%% + +// ISO/IEC 9075-2:2016(E), 7.2, +// +// Specify a row value. + +table_row_value_expression: + row_value_constructor { $$.v = $1.v as RowValueConstructor } + +contextually_typed_row_value_expression: + contextually_typed_row_value_constructor { + $$.v = $1.v as ContextuallyTypedRowValueConstructor + } + +row_value_predicand: + row_value_constructor_predicand { + $$.v = $1.v as RowValueConstructorPredicand + } + +%% diff --git a/vsql/std_table_value_constructor.v b/vsql/std_7_3_table_value_constructor.v similarity index 65% rename from vsql/std_table_value_constructor.v rename to vsql/std_7_3_table_value_constructor.v index 31a1c2b..90ee73a 100644 --- a/vsql/std_table_value_constructor.v +++ b/vsql/std_7_3_table_value_constructor.v @@ -2,27 +2,7 @@ module vsql // ISO/IEC 9075-2:2016(E), 7.3,
    // -// # Function -// // Specify a set of s to be constructed into a table. -// -// # Format -//~ -//~
    /* SimpleTable */ ::= -//~ VALUES -> table_value_constructor -//~ -//~ /* []RowValueConstructor */ ::= -//~
    -> row_value_expression_list_1 -//~ | -//~
    -> row_value_expression_list_2 -//~ -//~ /* []ContextuallyTypedRowValueConstructor */ ::= -//~ VALUES -> contextually_typed_table_value_constructor -//~ -//~ /* []ContextuallyTypedRowValueConstructor */ ::= -//~ -> contextually_typed_row_value_expression_list_1 -//~ | -//~ -> contextually_typed_row_value_expression_list_2 type SimpleTable = QuerySpecification | []RowValueConstructor @@ -42,36 +22,6 @@ fn (e SimpleTable) pstr(params map[string]Value) string { } } -fn parse_table_value_constructor(exprs []RowValueConstructor) !SimpleTable { - return exprs -} - -fn parse_row_value_expression_list_1(expr RowValueConstructor) ![]RowValueConstructor { - return [expr] -} - -fn parse_row_value_expression_list_2(element_list []RowValueConstructor, element RowValueConstructor) ![]RowValueConstructor { - mut new_list := element_list.clone() - new_list << element - - return new_list -} - -fn parse_contextually_typed_row_value_expression_list_1(element ContextuallyTypedRowValueConstructor) ![]ContextuallyTypedRowValueConstructor { - return [element] -} - -fn parse_contextually_typed_row_value_expression_list_2(list []ContextuallyTypedRowValueConstructor, element ContextuallyTypedRowValueConstructor) ![]ContextuallyTypedRowValueConstructor { - mut new_list := list.clone() - new_list << element - - return new_list -} - -fn parse_contextually_typed_table_value_constructor(e []ContextuallyTypedRowValueConstructor) ![]ContextuallyTypedRowValueConstructor { - return e -} - // A ValuesOperation provides a VALUES derived implicit table. struct ValuesOperation { rows []RowValueConstructor @@ -124,7 +74,7 @@ fn (o &ValuesOperation) str() string { fn (o &ValuesOperation) columns() Columns { mut c := Compiler{ - conn: o.conn + conn: o.conn params: o.params } e := o.rows[0] @@ -138,7 +88,7 @@ fn (o &ValuesOperation) columns() Columns { typ := (e.exprs[i].compile(mut c) or { panic(err) }).typ columns << Column{ name: column - typ: typ + typ: typ } } QueryExpression { @@ -149,7 +99,7 @@ fn (o &ValuesOperation) columns() Columns { CommonValueExpression, BooleanValueExpression { columns << Column{ name: column - typ: (e.compile(mut c) or { panic(err) }).typ + typ: (e.compile(mut c) or { panic(err) }).typ } } } @@ -172,7 +122,7 @@ fn (o &ValuesOperation) columns() Columns { name: Identifier{ sub_entity_name: 'COL${i}' } - typ: typ + typ: typ } } } @@ -186,7 +136,7 @@ fn (o &ValuesOperation) columns() Columns { name: Identifier{ sub_entity_name: 'COL1' } - typ: (e.compile(mut c) or { panic(err) }).typ + typ: (e.compile(mut c) or { panic(err) }).typ } } } @@ -196,7 +146,7 @@ fn (o &ValuesOperation) columns() Columns { fn (mut o ValuesOperation) execute(_ []Row) ![]Row { mut c := Compiler{ - conn: o.conn + conn: o.conn params: o.params } offset := int((o.offset.compile(mut c)!.run(mut o.conn, Row{}, o.params)!).f64_value()) diff --git a/vsql/std_7_3_table_value_constructor.y b/vsql/std_7_3_table_value_constructor.y new file mode 100644 index 0000000..265f83f --- /dev/null +++ b/vsql/std_7_3_table_value_constructor.y @@ -0,0 +1,30 @@ +%% + +table_value_constructor: + VALUES row_value_expression_list { + $$.v = SimpleTable($2.v as []RowValueConstructor) + } + +row_value_expression_list: + table_row_value_expression { $$.v = [$1.v as RowValueConstructor] } +| row_value_expression_list comma table_row_value_expression { + $$.v = append_list($1.v as []RowValueConstructor, + $3.v as RowValueConstructor) + } + +contextually_typed_table_value_constructor: + VALUES contextually_typed_row_value_expression_list { + $$.v = $2.v as []ContextuallyTypedRowValueConstructor + } + +contextually_typed_row_value_expression_list: + contextually_typed_row_value_expression { + $$.v = [$1.v as ContextuallyTypedRowValueConstructor] + } +| contextually_typed_row_value_expression_list comma + contextually_typed_row_value_expression { + $$.v = append_list($1.v as []ContextuallyTypedRowValueConstructor, + $3.v as ContextuallyTypedRowValueConstructor) + } + +%% diff --git a/vsql/std_7_4_table_expression.v b/vsql/std_7_4_table_expression.v new file mode 100644 index 0000000..509fecf --- /dev/null +++ b/vsql/std_7_4_table_expression.v @@ -0,0 +1,11 @@ +module vsql + +// ISO/IEC 9075-2:2016(E), 7.4,
    +// +// Specify a table or a grouped table. + +struct TableExpression { + from_clause TableReference + where_clause ?BooleanValueExpression + group_clause []Identifier +} diff --git a/vsql/std_7_4_table_expression.y b/vsql/std_7_4_table_expression.y new file mode 100644 index 0000000..c3755a8 --- /dev/null +++ b/vsql/std_7_4_table_expression.y @@ -0,0 +1,25 @@ +%% + +table_expression: + from_clause { + $$.v = TableExpression{$1.v as TableReference, none, []Identifier{}} + } +| from_clause where_clause { + $$.v = TableExpression{ + $1.v as TableReference + $2.v as BooleanValueExpression + []Identifier{} + } + } +| from_clause group_by_clause { + $$.v = TableExpression{$1.v as TableReference, none, $2.v as []Identifier} + } +| from_clause where_clause group_by_clause { + $$.v = TableExpression{ + $1.v as TableReference + $2.v as BooleanValueExpression + $3.v as []Identifier + } + } + +%% diff --git a/vsql/std_7_5_from_clause.y b/vsql/std_7_5_from_clause.y new file mode 100644 index 0000000..158887f --- /dev/null +++ b/vsql/std_7_5_from_clause.y @@ -0,0 +1,13 @@ +%% + +// ISO/IEC 9075-2:2016(E), 7.5, +// +// Specify a table derived from one or more tables. + +from_clause: + FROM table_reference_list { $$.v = $2.v as TableReference } + +table_reference_list: + table_reference { $$.v = $1.v as TableReference } + +%% diff --git a/vsql/std_7_6_table_reference.v b/vsql/std_7_6_table_reference.v new file mode 100644 index 0000000..de2893e --- /dev/null +++ b/vsql/std_7_6_table_reference.v @@ -0,0 +1,43 @@ +module vsql + +// ISO/IEC 9075-2:2016(E), 7.6,
    +// +// Reference a table. + +struct Correlation { + name Identifier + columns []Identifier +} + +fn (c Correlation) str() string { + if c.name.sub_entity_name == '' { + return '' + } + + mut s := ' AS ${c.name}' + + if c.columns.len > 0 { + mut columns := []string{} + for col in c.columns { + columns << col.sub_entity_name + } + + s += ' (${columns.join(', ')})' + } + + return s +} + +struct TablePrimary { + body TablePrimaryBody + correlation Correlation +} + +type TableReference = QualifiedJoin | TablePrimary + +struct QualifiedJoin { + left_table TableReference + join_type string // 'INNER', 'LEFT' or 'RIGHT' + right_table TableReference + specification BooleanValueExpression // ON condition +} diff --git a/vsql/std_7_6_table_reference.y b/vsql/std_7_6_table_reference.y new file mode 100644 index 0000000..24dc3b7 --- /dev/null +++ b/vsql/std_7_6_table_reference.y @@ -0,0 +1,84 @@ +%% + +table_reference: + table_factor { $$.v = TableReference($1.v as TablePrimary) } +| joined_table { $$.v = TableReference($1.v as QualifiedJoin) } + +qualified_join: + table_reference JOIN table_reference join_specification { + $$.v = QualifiedJoin{ + $1.v as TableReference + 'INNER' + $3.v as TableReference + $4.v as BooleanValueExpression + } + } +| table_reference join_type JOIN table_reference join_specification { + $$.v = QualifiedJoin{ + $1.v as TableReference + $2.v as string + $4.v as TableReference + $5.v as BooleanValueExpression + } + } + +table_factor: + table_primary { $$.v = $1.v as TablePrimary } + +table_primary: + table_or_query_name { + $$.v = TablePrimary{ + body: $1.v as Identifier + } + } +| derived_table { $$.v = $1.v as TablePrimary } +| derived_table correlation_or_recognition { + $$.v = TablePrimary{ + body: ($1.v as TablePrimary).body + correlation: $2.v as Correlation + } + } + +correlation_or_recognition: + correlation_name { + $$.v = Correlation{ + name: $1.v as Identifier + } + } +| AS correlation_name { + $$.v = Correlation{ + name: $2.v as Identifier + } + } +| correlation_name parenthesized_derived_column_list { + $$.v = Correlation{ + name: $1.v as Identifier + columns: $2.v as []Identifier + } + } +| AS correlation_name parenthesized_derived_column_list { + $$.v = Correlation{ + name: $2.v as Identifier + columns: $3.v as []Identifier + } + } + +derived_table: + table_subquery { $$.v = $1.v as TablePrimary } + +table_or_query_name: + table_name { $$.v = $1.v as Identifier } + +derived_column_list: + column_name_list { $$.v = $1.v as []Identifier } + +column_name_list: + column_name { $$.v = [$1.v as Identifier] } +| column_name_list comma column_name { + $$.v = append_list($1.v as []Identifier, $3.v as Identifier) + } + +parenthesized_derived_column_list: + left_paren derived_column_list right_paren { $$.v = $2.v } + +%% diff --git a/vsql/std_predicate.v b/vsql/std_8_1_predicate.v similarity index 68% rename from vsql/std_predicate.v rename to vsql/std_8_1_predicate.v index 5f0745d..2f73719 100644 --- a/vsql/std_predicate.v +++ b/vsql/std_8_1_predicate.v @@ -2,18 +2,7 @@ module vsql // ISO/IEC 9075-2:2016(E), 8.1, // -// # Function -// // Specify a condition that can be evaluated to give a boolean value. -// -// # Format -//~ -//~ /* Predicate */ ::= -//~ -> Predicate -//~ | -> Predicate -//~ | -> Predicate -//~ | -> Predicate -//~ | -> Predicate type Predicate = BetweenPredicate | CharacterLikePredicate diff --git a/vsql/std_8_1_predicate.y b/vsql/std_8_1_predicate.y new file mode 100644 index 0000000..4f72ede --- /dev/null +++ b/vsql/std_8_1_predicate.y @@ -0,0 +1,10 @@ +%% + +predicate: + comparison_predicate { $$.v = Predicate($1.v as ComparisonPredicate) } +| between_predicate { $$.v = Predicate($1.v as BetweenPredicate) } +| like_predicate { $$.v = Predicate($1.v as CharacterLikePredicate) } +| similar_predicate { $$.v = Predicate($1.v as SimilarPredicate) } +| null_predicate { $$.v = Predicate($1.v as NullPredicate) } + +%% diff --git a/vsql/std_search_condition.v b/vsql/std_8_21_search_condition.v similarity index 91% rename from vsql/std_search_condition.v rename to vsql/std_8_21_search_condition.v index d993e92..4cb7313 100644 --- a/vsql/std_search_condition.v +++ b/vsql/std_8_21_search_condition.v @@ -2,15 +2,8 @@ module vsql // ISO/IEC 9075-2:2016(E), 8.21, // -// # Function -// // Specify a condition that is True, False, or Unknown, depending on the value // of a . -// -// # Format -//~ -//~ /* BooleanValueExpression */ ::= -//~ // A WhereOperation executes a condition on each row, only passing through rows // that evaluate to TRUE. @@ -52,7 +45,7 @@ fn (mut o WhereOperation) execute(rows []Row) ![]Row { fn eval_as_bool(mut conn Connection, data Row, e BooleanValueExpression, params map[string]Value, tables map[string]Table) !bool { mut c := Compiler{ - conn: conn + conn: conn params: params tables: tables } diff --git a/vsql/std_8_21_search_condition.y b/vsql/std_8_21_search_condition.y new file mode 100644 index 0000000..83a01f2 --- /dev/null +++ b/vsql/std_8_21_search_condition.y @@ -0,0 +1,6 @@ +%% + +search_condition: + boolean_value_expression { $$.v = $1.v as BooleanValueExpression } + +%% diff --git a/vsql/std_comparison_predicate.v b/vsql/std_8_2_comparison_predicate.v similarity index 94% rename from vsql/std_comparison_predicate.v rename to vsql/std_8_2_comparison_predicate.v index 4143719..2c22968 100644 --- a/vsql/std_comparison_predicate.v +++ b/vsql/std_8_2_comparison_predicate.v @@ -4,26 +4,7 @@ import strings // ISO/IEC 9075-2:2016(E), 8.2, // -// # Function -// // Specify a comparison of two row values. -// -// # Format -//~ -//~ /* ComparisonPredicate */ ::= -//~ -> comparison -//~ -//~ /* ComparisonPredicatePart2 */ ::= -//~ -> comparison_part -//~ -//~ /* string */ ::= -//~ -//~ | -//~ | -//~ | -//~ | -//~ | - struct ComparisonPredicate { left RowValueConstructorPredicand op string @@ -39,7 +20,7 @@ fn (e ComparisonPredicate) compile(mut c Compiler) !CompileResult { compiled_right := e.right.compile(mut c)! return CompileResult{ - run: fn [e, compiled_left, compiled_right] (mut conn Connection, data Row, params map[string]Value) !Value { + run: fn [e, compiled_left, compiled_right] (mut conn Connection, data Row, params map[string]Value) !Value { mut left := compiled_left.run(mut conn, data, params)! mut right := compiled_right.run(mut conn, data, params)! @@ -73,7 +54,7 @@ fn (e ComparisonPredicate) compile(mut c Compiler) !CompileResult { } }) } - typ: new_type('BOOLEAN', 0, 0) + typ: new_type('BOOLEAN', 0, 0) contains_agg: compiled_left.contains_agg || compiled_right.contains_agg } } @@ -83,14 +64,6 @@ struct ComparisonPredicatePart2 { expr RowValueConstructorPredicand } -fn parse_comparison_part(op string, expr RowValueConstructorPredicand) !ComparisonPredicatePart2 { - return ComparisonPredicatePart2{op, expr} -} - -fn parse_comparison(expr RowValueConstructorPredicand, comp ComparisonPredicatePart2) !ComparisonPredicate { - return ComparisonPredicate{expr, comp.op, comp.expr} -} - enum CompareResult as i8 { is_unknown = 0 // same as NULL is_less = 1 diff --git a/vsql/std_8_2_comparison_predicate.y b/vsql/std_8_2_comparison_predicate.y new file mode 100644 index 0000000..cfe4553 --- /dev/null +++ b/vsql/std_8_2_comparison_predicate.y @@ -0,0 +1,29 @@ +%% + +comparison_predicate: + row_value_predicand comparison_predicate_part_2 { + comp := $2.v as ComparisonPredicatePart2 + $$.v = ComparisonPredicate{ + $1.v as RowValueConstructorPredicand + comp.op + comp.expr + } + } + +comparison_predicate_part_2: + comp_op row_value_predicand { + $$.v = ComparisonPredicatePart2{ + $1.v as string + $2.v as RowValueConstructorPredicand + } + } + +comp_op: + equals_operator { $$.v = $1.v as string } +| not_equals_operator { $$.v = $1.v as string } +| less_than_operator { $$.v = $1.v as string } +| greater_than_operator { $$.v = $1.v as string } +| less_than_or_equals_operator { $$.v = $1.v as string } +| greater_than_or_equals_operator { $$.v = $1.v as string } + +%% diff --git a/vsql/std_8_3_between_predicate.v b/vsql/std_8_3_between_predicate.v new file mode 100644 index 0000000..b5bc9ad --- /dev/null +++ b/vsql/std_8_3_between_predicate.v @@ -0,0 +1,64 @@ +module vsql + +// ISO/IEC 9075-2:2016(E), 8.3, +// +// Specify a range comparison. + +struct BetweenPredicate { + not bool + symmetric bool + expr RowValueConstructorPredicand + left RowValueConstructorPredicand + right RowValueConstructorPredicand +} + +fn (e BetweenPredicate) pstr(params map[string]Value) string { + return '${e.expr.pstr(params)} ' + if e.not { + 'NOT ' + } else { + '' + } + 'BETWEEN ' + if e.symmetric { + 'SYMMETRIC ' + } else { + '' + } + '${e.left.pstr(params)} AND ${e.right.pstr(params)}' +} + +fn (e BetweenPredicate) compile(mut c Compiler) !CompileResult { + compiled_expr := e.expr.compile(mut c)! + compiled_left := e.left.compile(mut c)! + compiled_right := e.right.compile(mut c)! + + return CompileResult{ + run: fn [e, compiled_expr, compiled_left, compiled_right] (mut conn Connection, data Row, params map[string]Value) !Value { + expr := compiled_expr.run(mut conn, data, params)! + mut left := compiled_left.run(mut conn, data, params)! + mut right := compiled_right.run(mut conn, data, params)! + + // SYMMETRIC operands might need to be swapped. + cmp := compare(left, right)! + if e.symmetric && cmp == .is_greater { + left, right = right, left + } + + lower := compare(expr, left)! + upper := compare(expr, right)! + + if lower == .is_unknown || upper == .is_unknown { + return new_unknown_value() + } + + mut result := (lower == .is_greater || lower == .is_equal) + && (upper == .is_less || upper == .is_equal) + + if e.not { + result = !result + } + + return new_boolean_value(result) + } + typ: new_type('BOOLEAN', 0, 0) + contains_agg: compiled_expr.contains_agg || compiled_left.contains_agg + || compiled_left.contains_agg + } +} diff --git a/vsql/std_8_3_between_predicate.y b/vsql/std_8_3_between_predicate.y new file mode 100644 index 0000000..ed9707e --- /dev/null +++ b/vsql/std_8_3_between_predicate.y @@ -0,0 +1,42 @@ +%% + +between_predicate: + row_value_predicand between_predicate_part_2 { + between := $2.v as BetweenPredicate + $$.v = BetweenPredicate{ + not: between.not + symmetric: between.symmetric + expr: $1.v as RowValueConstructorPredicand + left: between.left + right: between.right + } + } + +between_predicate_part_2: + between_predicate_part_1 row_value_predicand AND row_value_predicand { + $$.v = BetweenPredicate{ + not: !($1.v as bool) + symmetric: false + left: $2.v as RowValueConstructorPredicand + right: $4.v as RowValueConstructorPredicand + } + } +| between_predicate_part_1 is_symmetric row_value_predicand AND + row_value_predicand { + $$.v = BetweenPredicate{ + not: !($1.v as bool) + symmetric: $2.v as bool + left: $3.v as RowValueConstructorPredicand + right: $5.v as RowValueConstructorPredicand + } + } + +between_predicate_part_1: + BETWEEN { $$.v = true } +| NOT BETWEEN { $$.v = false } + +is_symmetric: + SYMMETRIC { $$.v = true } +| ASYMMETRIC { $$.v = false } + +%% diff --git a/vsql/std_like_predicate.v b/vsql/std_8_5_like_predicate.v similarity index 58% rename from vsql/std_like_predicate.v rename to vsql/std_8_5_like_predicate.v index d22b97c..9b47a48 100644 --- a/vsql/std_like_predicate.v +++ b/vsql/std_8_5_like_predicate.v @@ -4,24 +4,7 @@ import regex // ISO/IEC 9075-2:2016(E), 8.5, // -// # Function -// // Specify a pattern-match comparison. -// -// # Format -//~ -//~ /* CharacterLikePredicate */ ::= -//~ -//~ -//~ /* CharacterLikePredicate */ ::= -//~ -> like_pred -//~ -//~ /* CharacterLikePredicate */ ::= -//~ LIKE -> like -//~ | NOT LIKE -> not_like -//~ -//~ /* CharacterValueExpression */ ::= -//~ // CharacterLikePredicate for "LIKE" and "NOT LIKE". struct CharacterLikePredicate { @@ -49,7 +32,7 @@ fn (e CharacterLikePredicate) compile(mut c Compiler) !CompileResult { compiled_l := l.compile(mut c)! return CompileResult{ - run: fn [e, compiled_l, compiled_right] (mut conn Connection, data Row, params map[string]Value) !Value { + run: fn [e, compiled_l, compiled_right] (mut conn Connection, data Row, params map[string]Value) !Value { left := compiled_l.run(mut conn, data, params)! right := compiled_right.run(mut conn, data, params)! @@ -70,22 +53,10 @@ fn (e CharacterLikePredicate) compile(mut c Compiler) !CompileResult { return new_boolean_value(result) } - typ: new_type('BOOLEAN', 0, 0) + typ: new_type('BOOLEAN', 0, 0) contains_agg: compiled_right.contains_agg || compiled_l.contains_agg } } return compiled_right } - -fn parse_like_pred(left RowValueConstructorPredicand, like CharacterLikePredicate) !CharacterLikePredicate { - return CharacterLikePredicate{left, like.right, like.not} -} - -fn parse_like(expr CharacterValueExpression) !CharacterLikePredicate { - return CharacterLikePredicate{none, expr, false} -} - -fn parse_not_like(expr CharacterValueExpression) !CharacterLikePredicate { - return CharacterLikePredicate{none, expr, true} -} diff --git a/vsql/std_8_5_like_predicate.y b/vsql/std_8_5_like_predicate.y new file mode 100644 index 0000000..9077f9c --- /dev/null +++ b/vsql/std_8_5_like_predicate.y @@ -0,0 +1,27 @@ +%% + +like_predicate: + character_like_predicate { $$.v = $1.v as CharacterLikePredicate } + +character_like_predicate: + row_value_predicand character_like_predicate_part_2 { + like := $2.v as CharacterLikePredicate + $$.v = CharacterLikePredicate{ + $1.v as RowValueConstructorPredicand + like.right + like.not + } + } + +character_like_predicate_part_2: + LIKE character_pattern { + $$.v = CharacterLikePredicate{none, $2.v as CharacterValueExpression, false} + } +| NOT LIKE character_pattern { + $$.v = CharacterLikePredicate{none, $3.v as CharacterValueExpression, true} + } + +character_pattern: + character_value_expression { $$.v = $1.v as CharacterValueExpression } + +%% diff --git a/vsql/std_similar_predicate.v b/vsql/std_8_6_similar_predicate.v similarity index 58% rename from vsql/std_similar_predicate.v rename to vsql/std_8_6_similar_predicate.v index af7373f..751ba0d 100644 --- a/vsql/std_similar_predicate.v +++ b/vsql/std_8_6_similar_predicate.v @@ -4,21 +4,7 @@ import regex // ISO/IEC 9075-2:2016(E), 8.6, // -// # Function -// // Specify a character string similarity by means of a regular expression. -// -// # Format -//~ -//~ /* SimilarPredicate */ ::= -//~ -> similar_pred -//~ -//~ /* SimilarPredicate */ ::= -//~ SIMILAR TO -> similar -//~ | NOT SIMILAR TO -> not_similar -//~ -//~ /* CharacterValueExpression */ ::= -//~ // SimilarPredicate for "SIMILAR TO" and "NOT SIMILAR TO". struct SimilarPredicate { @@ -45,7 +31,7 @@ fn (e SimilarPredicate) compile(mut c Compiler) !CompileResult { compiled_right := e.right.compile(mut c)! return CompileResult{ - run: fn [e, compiled_l, compiled_right] (mut conn Connection, data Row, params map[string]Value) !Value { + run: fn [e, compiled_l, compiled_right] (mut conn Connection, data Row, params map[string]Value) !Value { left := compiled_l.run(mut conn, data, params)! right := compiled_right.run(mut conn, data, params)! @@ -62,22 +48,10 @@ fn (e SimilarPredicate) compile(mut c Compiler) !CompileResult { return new_boolean_value(result) } - typ: new_type('BOOLEAN', 0, 0) + typ: new_type('BOOLEAN', 0, 0) contains_agg: compiled_l.contains_agg || compiled_right.contains_agg } } return e.right.compile(mut c)! } - -fn parse_similar_pred(left RowValueConstructorPredicand, like SimilarPredicate) !SimilarPredicate { - return SimilarPredicate{left, like.right, like.not} -} - -fn parse_similar(expr CharacterValueExpression) !SimilarPredicate { - return SimilarPredicate{none, expr, false} -} - -fn parse_not_similar(expr CharacterValueExpression) !SimilarPredicate { - return SimilarPredicate{none, expr, true} -} diff --git a/vsql/std_8_6_similar_predicate.y b/vsql/std_8_6_similar_predicate.y new file mode 100644 index 0000000..92d92e0 --- /dev/null +++ b/vsql/std_8_6_similar_predicate.y @@ -0,0 +1,20 @@ +%% + +similar_predicate: + row_value_predicand similar_predicate_part_2 { + like := $2.v as SimilarPredicate + $$.v = SimilarPredicate{$1.v as RowValueConstructorPredicand, like.right, like.not} + } + +similar_predicate_part_2: + SIMILAR TO similar_pattern { + $$.v = SimilarPredicate{none, $3.v as CharacterValueExpression, false} + } +| NOT SIMILAR TO similar_pattern { + $$.v = SimilarPredicate{none, $4.v as CharacterValueExpression, true} + } + +similar_pattern: + character_value_expression { $$.v = $1.v as CharacterValueExpression } + +%% diff --git a/vsql/std_null_predicate.v b/vsql/std_8_8_null_predicate.v similarity index 58% rename from vsql/std_null_predicate.v rename to vsql/std_8_8_null_predicate.v index e80cba1..a21ad23 100644 --- a/vsql/std_null_predicate.v +++ b/vsql/std_8_8_null_predicate.v @@ -2,18 +2,7 @@ module vsql // ISO/IEC 9075-2:2016(E), 8.8, // -// # Function -// // Specify a test for a null value. -// -// # Format -//~ -//~ /* NullPredicate */ ::= -//~ -> null_predicate -//~ -//~ /* bool */ ::= -//~ IS NULL -> yes -//~ | IS NOT NULL -> no // NullPredicate for "IS NULL" and "IS NOT NULL". struct NullPredicate { @@ -33,7 +22,7 @@ fn (e NullPredicate) compile(mut c Compiler) !CompileResult { compiled := e.expr.compile(mut c)! return CompileResult{ - run: fn [e, compiled] (mut conn Connection, data Row, params map[string]Value) !Value { + run: fn [e, compiled] (mut conn Connection, data Row, params map[string]Value) !Value { value := compiled.run(mut conn, data, params)! if e.not { @@ -42,11 +31,7 @@ fn (e NullPredicate) compile(mut c Compiler) !CompileResult { return new_boolean_value(value.is_null) } - typ: new_type('BOOLEAN', 0, 0) + typ: new_type('BOOLEAN', 0, 0) contains_agg: compiled.contains_agg } } - -fn parse_null_predicate(expr RowValueConstructorPredicand, is_null bool) !NullPredicate { - return NullPredicate{expr, !is_null} -} diff --git a/vsql/std_8_8_null_predicate.y b/vsql/std_8_8_null_predicate.y new file mode 100644 index 0000000..65f123a --- /dev/null +++ b/vsql/std_8_8_null_predicate.y @@ -0,0 +1,12 @@ +%% + +null_predicate: + row_value_predicand null_predicate_part_2 { + $$.v = NullPredicate{$1.v as RowValueConstructorPredicand, !($2.v as bool)} + } + +null_predicate_part_2: + IS NULL { $$.v = true } +| IS NOT NULL { $$.v = false } + +%% diff --git a/vsql/std_store_assignment.v b/vsql/std_9_2_store_assignment.v similarity index 98% rename from vsql/std_store_assignment.v rename to vsql/std_9_2_store_assignment.v index 096bdc2..6bd35c4 100644 --- a/vsql/std_store_assignment.v +++ b/vsql/std_9_2_store_assignment.v @@ -4,8 +4,6 @@ import strings // ISO/IEC 9075-2:2016(E), 9.2, Store assignment // -// # Function -// // Specify rules for assignments where the target permits null without the use // of indicator parameters or indicator variables, such as storing SQL-data or // setting the value of SQL parameters. @@ -557,17 +555,17 @@ fn cast_timestamp_without_to_timestamp_without(conn &Connection, v Value, to Typ fn check_numeric_range(x Numeric, typ SQLType) ! { match typ { .is_smallint { - if x.less_than(vsql.min_smallint) || x.greater_than(vsql.max_smallint) { + if x.less_than(min_smallint) || x.greater_than(max_smallint) { return sqlstate_22003() } } .is_integer { - if x.less_than(vsql.min_integer) || x.greater_than(vsql.max_integer) { + if x.less_than(min_integer) || x.greater_than(max_integer) { return sqlstate_22003() } } .is_bigint { - if x.less_than(vsql.min_bigint) || x.greater_than(vsql.max_bigint) { + if x.less_than(min_bigint) || x.greater_than(max_bigint) { return sqlstate_22003() } } diff --git a/vsql/std_between_predicate.v b/vsql/std_between_predicate.v index 26c9e87..bee8900 100644 --- a/vsql/std_between_predicate.v +++ b/vsql/std_between_predicate.v @@ -53,7 +53,7 @@ fn (e BetweenPredicate) compile(mut c Compiler) !CompileResult { compiled_right := e.right.compile(mut c)! return CompileResult{ - run: fn [e, compiled_expr, compiled_left, compiled_right] (mut conn Connection, data Row, params map[string]Value) !Value { + run: fn [e, compiled_expr, compiled_left, compiled_right] (mut conn Connection, data Row, params map[string]Value) !Value { expr := compiled_expr.run(mut conn, data, params)! mut left := compiled_left.run(mut conn, data, params)! mut right := compiled_right.run(mut conn, data, params)! @@ -80,7 +80,7 @@ fn (e BetweenPredicate) compile(mut c Compiler) !CompileResult { return new_boolean_value(result) } - typ: new_type('BOOLEAN', 0, 0) + typ: new_type('BOOLEAN', 0, 0) contains_agg: compiled_expr.contains_agg || compiled_left.contains_agg || compiled_left.contains_agg } @@ -88,11 +88,11 @@ fn (e BetweenPredicate) compile(mut c Compiler) !CompileResult { fn parse_between(expr RowValueConstructorPredicand, between BetweenPredicate) !BetweenPredicate { return BetweenPredicate{ - not: between.not + not: between.not symmetric: between.symmetric - expr: expr - left: between.left - right: between.right + expr: expr + left: between.left + right: between.right } } @@ -103,9 +103,9 @@ fn parse_between_1(is_true bool, left RowValueConstructorPredicand, right RowVal fn parse_between_2(is_true bool, symmetric bool, left RowValueConstructorPredicand, right RowValueConstructorPredicand) !BetweenPredicate { return BetweenPredicate{ - not: !is_true + not: !is_true symmetric: symmetric - left: left - right: right + left: left + right: right } } diff --git a/vsql/std_column_definition.v b/vsql/std_column_definition.v deleted file mode 100644 index ce7d761..0000000 --- a/vsql/std_column_definition.v +++ /dev/null @@ -1,31 +0,0 @@ -module vsql - -// ISO/IEC 9075-2:2016(E), 11.4, -// -// # Function -// -// Define a column of a base table. -// -// # Format -//~ -//~ /* TableElement */ ::= -//~ -> column_definition_1 -//~ | -//~ -> column_definition_2 -//~ -//~ /* Type */ ::= -//~ -//~ -//~ /* bool */ ::= -//~ -//~ -//~ /* bool */ ::= -//~ NOT NULL -> yes - -fn parse_column_definition_1(column_name Identifier, data_type Type) !TableElement { - return Column{column_name, data_type, false} -} - -fn parse_column_definition_2(column_name Identifier, data_type Type, constraint bool) !TableElement { - return Column{column_name, data_type, constraint} -} diff --git a/vsql/std_column_reference.v b/vsql/std_column_reference.v deleted file mode 100644 index 09a8dff..0000000 --- a/vsql/std_column_reference.v +++ /dev/null @@ -1,16 +0,0 @@ -module vsql - -// ISO/IEC 9075-2:2016(E), 6.7, -// -// # Function -// -// Reference a column. -// -// # Format -//~ -//~ /* Identifier */ ::= -//~ -> column_reference - -fn parse_column_reference(identifier IdentifierChain) !Identifier { - return new_column_identifier(identifier.identifier) -} diff --git a/vsql/std_cursor_specification.v b/vsql/std_cursor_specification.v deleted file mode 100644 index a6920fa..0000000 --- a/vsql/std_cursor_specification.v +++ /dev/null @@ -1,12 +0,0 @@ -module vsql - -// ISO/IEC 9075-2:2016(E), 14.3, -// -// # Function -// -// Define a result set. -// -// # Format -//~ -//~ /* Stmt */ ::= -//~ -> Stmt diff --git a/vsql/std_data_type.v b/vsql/std_data_type.v deleted file mode 100644 index 36ca08b..0000000 --- a/vsql/std_data_type.v +++ /dev/null @@ -1,218 +0,0 @@ -module vsql - -// ISO/IEC 9075-2:2016(E), 6.1, -// -// # Function -// -// Specify a data type. -// -// # Format -//~ -//~ /* Type */ ::= -//~ -//~ -//~ /* Type */ ::= -//~ -//~ | -//~ | -//~ | -//~ -//~ /* Type */ ::= -//~ CHARACTER -> character -//~ | CHARACTER -> character_n -//~ | CHAR -> character -//~ | CHAR -> character_n -//~ | CHARACTER VARYING -> varchar -//~ | CHAR VARYING -> varchar -//~ | VARCHAR -> varchar -//~ -//~ /* Type */ ::= -//~ -//~ | -//~ -//~ /* Type */ ::= -//~ NUMERIC -> numeric1 -//~ | NUMERIC -> numeric2 -//~ | NUMERIC -> numeric3 -//~ | DECIMAL -> decimal1 -//~ | DECIMAL -> decimal2 -//~ | DECIMAL -> decimal3 -//~ | SMALLINT -> smallint -//~ | INTEGER -> integer -//~ | INT -> integer -//~ | BIGINT -> bigint -//~ -//~ /* Type */ ::= -//~ FLOAT -> float -//~ | FLOAT -> float_n -//~ | REAL -> real -//~ | DOUBLE PRECISION -> double_precision -//~ -//~ /* string */ ::= -//~ -//~ -//~ /* string */ ::= -//~ -//~ -//~ /* string */ ::= -//~ CHARACTERS -//~ | OCTETS -//~ -//~ /* string */ ::= -//~ -//~ -//~ /* string */ ::= -//~ -//~ -//~ /* Type */ ::= -//~ BOOLEAN -> boolean_type -//~ -//~ /* Type */ ::= -//~ DATE -> date_type -//~ | TIME -> time_type -//~ | TIME
    diff --git a/vsql/std_from_clause.v b/vsql/std_from_clause.v deleted file mode 100644 index f44ba84..0000000 --- a/vsql/std_from_clause.v +++ /dev/null @@ -1,19 +0,0 @@ -module vsql - -// ISO/IEC 9075-2:2016(E), 7.5, -// -// # Function -// -// Specify a table derived from one or more tables. -// -// # Format -//~ -//~ /* TableReference */ ::= -//~ FROM
    -> from_clause -//~ -//~
    /* TableReference */ ::= -//~
    - -fn parse_from_clause(table TableReference) !TableReference { - return table -} diff --git a/vsql/std_identifier_chain.v b/vsql/std_identifier_chain.v deleted file mode 100644 index 0a258a0..0000000 --- a/vsql/std_identifier_chain.v +++ /dev/null @@ -1,30 +0,0 @@ -module vsql - -// ISO/IEC 9075-2:2016(E), 6.6, -// -// # Function -// -// Disambiguate a -separated chain of identifiers. -// -// # Format -//~ -//~ /* IdentifierChain */ ::= -//~ -//~ | -> identifier_chain -//~ -//~ /* IdentifierChain */ ::= -//~ - -// IdentifierChain wraps a single string that contains the chain of one or more -// identifiers, such as: "Foo".bar."BAZ" -struct IdentifierChain { - identifier string -} - -fn (identifier IdentifierChain) str() string { - return identifier.identifier -} - -fn parse_identifier_chain(a IdentifierChain, b IdentifierChain) !IdentifierChain { - return IdentifierChain{a.identifier + '.' + b.identifier} -} diff --git a/vsql/std_literal.v b/vsql/std_literal.v deleted file mode 100644 index d78afcc..0000000 --- a/vsql/std_literal.v +++ /dev/null @@ -1,176 +0,0 @@ -module vsql - -import math.big -import math - -// ISO/IEC 9075-2:2016(E), 5.3, -// -// # Function -// -// Specify a non-null value. -// -// # Format -//~ -//~ /* Value */ ::= -//~ -//~ | -//~ -//~ /* Value */ ::= -//~ -//~ | -//~ -//~ /* Value */ ::= -//~ -//~ | -//~ | -//~ -//~ /* Value */ ::= -//~ ^string -//~ -//~ /* Value */ ::= -//~ -//~ | -> signed_numeric_literal_2 -//~ -//~ /* Value */ ::= -//~ -//~ | -//~ -//~ /* Value */ ::= -//~ -> exact_numeric_literal_1 -//~ | -> exact_numeric_literal_2 -//~ | -> exact_numeric_literal_3 -//~ | -> exact_numeric_literal_4 -//~ -//~ /* string */ ::= -//~ -//~ | -//~ -//~ /* Value */ ::= -//~ E -> approximate_numeric_literal -//~ -//~ /* Value */ ::= -//~ -//~ -//~ /* Value */ ::= -//~ -//~ -//~ /* Value */ ::= -//~ -> signed_integer_1 -//~ | -> signed_integer_2 -//~ -//~ /* string */ ::= -//~ ^integer -//~ -//~ /* Value */ ::= -//~ -//~ |
    /* RowValueConstructor */ ::= -//~ -//~ -//~ /* ContextuallyTypedRowValueConstructor */ ::= -//~ -//~ -//~ /* RowValueConstructorPredicand */ ::= -//~ diff --git a/vsql/std_sequence_generator_definition.v b/vsql/std_sequence_generator_definition.v index bb2bf08..3815834 100644 --- a/vsql/std_sequence_generator_definition.v +++ b/vsql/std_sequence_generator_definition.v @@ -116,7 +116,7 @@ fn parse_sequence_generator_definition_1(generator_name Identifier) !Stmt { fn parse_sequence_generator_definition_2(generator_name Identifier, options []SequenceGeneratorOption) !Stmt { return SequenceGeneratorDefinition{ - name: generator_name + name: generator_name options: options } } @@ -174,7 +174,7 @@ fn (stmt SequenceGeneratorDefinition) execute(mut conn Connection, params map[st } mut c := Compiler{ - conn: conn + conn: conn params: params } mut catalog := conn.catalog() @@ -236,14 +236,14 @@ fn (stmt SequenceGeneratorDefinition) execute(mut conn Connection, params map[st } sequence := Sequence{ - name: sequence_name + name: sequence_name current_value: current_value - increment_by: increment_by - cycle: cycle + increment_by: increment_by + cycle: cycle has_min_value: has_min_value - min_value: min_value + min_value: min_value has_max_value: has_max_value - max_value: max_value + max_value: max_value } catalog.storage.create_sequence(sequence)! diff --git a/vsql/std_set_clause_list.v b/vsql/std_set_clause_list.v deleted file mode 100644 index 30e2873..0000000 --- a/vsql/std_set_clause_list.v +++ /dev/null @@ -1,64 +0,0 @@ -module vsql - -// ISO/IEC 9075-2:2016(E), 14.15, -// -// # Function -// -// Specify a list of updates. -// -// # Format -//~ -//~ /* map[string]UpdateSource */ ::= -//~ -//~ | -> set_clause_append -//~ -//~ /* map[string]UpdateSource */ ::= -//~ -> set_clause -//~ -//~ /* Identifier */ ::= -//~ -//~ -//~ /* Identifier */ ::= -//~ -//~ -//~ /* UpdateSource */ ::= -//~ -> UpdateSource -//~ | -> UpdateSource -//~ -//~ /* Identifier */ ::= -//~ - -type UpdateSource = NullSpecification | ValueExpression - -fn (e UpdateSource) pstr(params map[string]Value) string { - return match e { - ValueExpression, NullSpecification { - e.pstr(params) - } - } -} - -fn (e UpdateSource) compile(mut c Compiler) !CompileResult { - match e { - ValueExpression, NullSpecification { - return e.compile(mut c)! - } - } -} - -fn parse_set_clause_append(set_clause_list map[string]UpdateSource, set_clause map[string]UpdateSource) !map[string]UpdateSource { - mut new_set_clause_list := set_clause_list.clone() - - // Even though there will only be one of these. - for k, v in set_clause { - new_set_clause_list[k] = v - } - - return new_set_clause_list -} - -fn parse_set_clause(target Identifier, update_source UpdateSource) !map[string]UpdateSource { - return { - target.str(): update_source - } -} diff --git a/vsql/std_sort_specification_list.v b/vsql/std_sort_specification_list.v deleted file mode 100644 index d107654..0000000 --- a/vsql/std_sort_specification_list.v +++ /dev/null @@ -1,56 +0,0 @@ -module vsql - -// ISO/IEC 9075-2:2016(E), 10.10, -// -// # Function -// -// Specify a sort order. -// -// # Format -//~ -//~ /* []SortSpecification */ ::= -//~ -> sort_list_1 -//~ | -> sort_list_2 -//~ -//~ /* SortSpecification */ ::= -//~ -> sort_1 -//~ | -> sort_2 -//~ -//~ /* ValueExpression */ ::= -//~ -//~ -//~ /* bool */ ::= -//~ ASC -> yes -//~ | DESC -> no - -struct SortSpecification { - expr ValueExpression - is_asc bool -} - -fn (e SortSpecification) pstr(params map[string]Value) string { - if e.is_asc { - return '${e.expr.pstr(params)} ASC' - } - - return '${e.expr.pstr(params)} DESC' -} - -fn parse_sort_list_1(spec SortSpecification) ![]SortSpecification { - return [spec] -} - -fn parse_sort_list_2(specs []SortSpecification, spec SortSpecification) ![]SortSpecification { - mut specs2 := specs.clone() - specs2 << spec - - return specs2 -} - -fn parse_sort_1(expr ValueExpression) !SortSpecification { - return SortSpecification{expr, true} -} - -fn parse_sort_2(expr ValueExpression, is_asc bool) !SortSpecification { - return SortSpecification{expr, is_asc} -} diff --git a/vsql/std_sql_procedure_statement.v b/vsql/std_sql_procedure_statement.v deleted file mode 100644 index 49923ba..0000000 --- a/vsql/std_sql_procedure_statement.v +++ /dev/null @@ -1,33 +0,0 @@ -module vsql - -// ISO/IEC 9075-2:2016(E), 13.4, -// -// # Function -// -// Define all of the SQL-statements that are s. -// -// # Format -//~ -//~ /* Stmt */ ::= -//~ -//~ | -//~ -//~ /* Stmt */ ::= -//~ -//~ |
    -//~ | -//~ -//~ /* Stmt */ ::= -//~ -//~ | -//~ | -> Stmt -//~ | -//~ -//~ /* Stmt */ ::= -//~ -//~ | -//~ | -//~ -//~ /* Stmt */ ::= -//~ -//~ | diff --git a/vsql/std_sql_terminal_character.v b/vsql/std_sql_terminal_character.v deleted file mode 100644 index 96259f7..0000000 --- a/vsql/std_sql_terminal_character.v +++ /dev/null @@ -1,38 +0,0 @@ -module vsql - -// ISO/IEC 9075-2:2016(E), 5.1, -// -// # Function -// -// Define the terminal symbols of the SQL language and the elements of strings. -// -// # Format -//~ -//~ ::= "(" -//~ -//~ ::= ")" -//~ -//~ /* string */ ::= -//~ "*" -//~ -//~ /* string */ ::= -//~ "+" -//~ -//~ ::= "," -//~ -//~ /* string */ ::= -//~ "-" -//~ -//~ ::= "." -//~ -//~ /* string */ ::= -//~ "/" -//~ -//~ ::= -//~ ":" -//~ -//~ ::= "<" -//~ -//~ ::= "=" -//~ -//~ ::= ">" diff --git a/vsql/std_string_value_function.v b/vsql/std_string_value_function.v index dddb84d..ccc07bb 100644 --- a/vsql/std_string_value_function.v +++ b/vsql/std_string_value_function.v @@ -108,8 +108,8 @@ fn (e CharacterSubstringFunction) compile(mut c Compiler) !CompileResult { f.compile(mut c)! } else { CompileResult{ - run: unsafe { nil } - typ: Type{} + run: unsafe { nil } + typ: Type{} contains_agg: false } } @@ -117,14 +117,14 @@ fn (e CharacterSubstringFunction) compile(mut c Compiler) !CompileResult { f.compile(mut c)! } else { CompileResult{ - run: unsafe { nil } - typ: Type{} + run: unsafe { nil } + typ: Type{} contains_agg: false } } return CompileResult{ - run: fn [e, compiled_value, compiled_from, compiled_for] (mut conn Connection, data Row, params map[string]Value) !Value { + run: fn [e, compiled_value, compiled_from, compiled_for] (mut conn Connection, data Row, params map[string]Value) !Value { value := compiled_value.run(mut conn, data, params)! mut from := 0 @@ -158,7 +158,7 @@ fn (e CharacterSubstringFunction) compile(mut c Compiler) !CompileResult { return new_varchar_value(value.string_value().substr(from, from + @for)) } - typ: new_type('CHARACTER VARYING', 0, 0) + typ: new_type('CHARACTER VARYING', 0, 0) contains_agg: compiled_value.contains_agg } } @@ -180,7 +180,7 @@ fn (e TrimFunction) compile(mut c Compiler) !CompileResult { compiled_character := e.character.compile(mut c)! return CompileResult{ - run: fn [e, compiled_source, compiled_character] (mut conn Connection, data Row, params map[string]Value) !Value { + run: fn [e, compiled_source, compiled_character] (mut conn Connection, data Row, params map[string]Value) !Value { source := compiled_source.run(mut conn, data, params)! character := compiled_character.run(mut conn, data, params)! @@ -194,7 +194,7 @@ fn (e TrimFunction) compile(mut c Compiler) !CompileResult { return new_varchar_value(source.string_value().trim(character.string_value())) } - typ: new_type('CHARACTER VARYING', 0, 0) + typ: new_type('CHARACTER VARYING', 0, 0) contains_agg: compiled_source.contains_agg || compiled_character.contains_agg } } diff --git a/vsql/std_subquery.v b/vsql/std_subquery.v deleted file mode 100644 index 4c3e939..0000000 --- a/vsql/std_subquery.v +++ /dev/null @@ -1,26 +0,0 @@ -module vsql - -// ISO/IEC 9075-2:2016(E), 7.19, -// -// # Function -// -// Specify a scalar value, a row, or a table derived from a . -// -// # Format -//~ -//~ /* QueryExpression */ ::= -//~ -//~ -//~
    /* TablePrimary */ ::= -//~ -//~ -//~ /* TablePrimaryBody */ ::= -//~ -> subquery - -type TablePrimaryBody = Identifier | QueryExpression - -fn parse_subquery(stmt QueryExpression) !TablePrimary { - return TablePrimary{ - body: stmt - } -} diff --git a/vsql/std_table_constraint_definition.v b/vsql/std_table_constraint_definition.v deleted file mode 100644 index 75c3a56..0000000 --- a/vsql/std_table_constraint_definition.v +++ /dev/null @@ -1,15 +0,0 @@ -module vsql - -// ISO/IEC 9075-2:2016(E), 11.6,
    -// -// # Function -// -// Specify an integrity constraint. -// -// # Format -//~ -//~
    /* TableElement */ ::= -//~
    -//~ -//~
    /* TableElement */ ::= -//~ diff --git a/vsql/std_table_expression.v b/vsql/std_table_expression.v deleted file mode 100644 index 91216c7..0000000 --- a/vsql/std_table_expression.v +++ /dev/null @@ -1,37 +0,0 @@ -module vsql - -// ISO/IEC 9075-2:2016(E), 7.4,
    -// -// # Function -// -// Specify a table or a grouped table. -// -// # Format -//~ -//~
    /* TableExpression */ ::= -//~ -> table_expression -//~ | -> table_expression_where -//~ | -> table_expression_group -//~ | -> table_expression_where_group - -struct TableExpression { - from_clause TableReference - where_clause ?BooleanValueExpression - group_clause []Identifier -} - -fn parse_table_expression(from_clause TableReference) !TableExpression { - return TableExpression{from_clause, none, []Identifier{}} -} - -fn parse_table_expression_group(from_clause TableReference, group []Identifier) !TableExpression { - return TableExpression{from_clause, none, group} -} - -fn parse_table_expression_where(from_clause TableReference, where BooleanValueExpression) !TableExpression { - return TableExpression{from_clause, where, []Identifier{}} -} - -fn parse_table_expression_where_group(from_clause TableReference, where BooleanValueExpression, group []Identifier) !TableExpression { - return TableExpression{from_clause, where, group} -} diff --git a/vsql/std_table_reference.v b/vsql/std_table_reference.v index facadcd..7afb706 100644 --- a/vsql/std_table_reference.v +++ b/vsql/std_table_reference.v @@ -105,7 +105,7 @@ fn parse_table_primary_identifier(name Identifier) !TablePrimary { fn parse_table_primary_derived_2(body TablePrimary, correlation Correlation) !TablePrimary { return TablePrimary{ - body: body.body + body: body.body correlation: correlation } } @@ -128,7 +128,7 @@ fn parse_correlation_1(name Identifier) !Correlation { fn parse_correlation_2(name Identifier, columns []Identifier) !Correlation { return Correlation{ - name: name + name: name columns: columns } } diff --git a/vsql/std_token_and_separator.v b/vsql/std_token_and_separator.v deleted file mode 100644 index 5161dea..0000000 --- a/vsql/std_token_and_separator.v +++ /dev/null @@ -1,323 +0,0 @@ -module vsql - -// ISO/IEC 9075-2:2016(E), 5.2, and -// -// # Function -// -// Specify lexical units (tokens and separators) that participate in SQL -// language. -// -// # Format -//~ -//~ ::= "||" -// -// is added on top of the original to -// allow non-reserved words to be used as identifiers. As far as I can tell, -// only s are restricted. See "9075-2:2016 5.2 #26". -// -//~ -//~ /* IdentifierChain */ ::= -//~ -//~ | -> string_identifier -//~ -//~ /* IdentifierChain */ ::= -//~ -//~ -//~ /* IdentifierChain */ ::= -//~ ^identifier -//~ -//~ ::= "<>" -//~ -//~ ::= ">=" -//~ -//~ ::= "<=" -//~ -//~ /* string */ ::= -//~ A -//~ | ABSOLUTE -//~ | ACTION -//~ | ADA -//~ | ADD -//~ | ADMIN -//~ | AFTER -//~ | ALWAYS -//~ | ASC -//~ | ASSERTION -//~ | ASSIGNMENT -//~ | ATTRIBUTE -//~ | ATTRIBUTES -//~ | BEFORE -//~ | BERNOULLI -//~ | BREADTH -//~ | C -//~ | CASCADE -//~ | CATALOG -//~ | CATALOG_NAME -//~ | CHAIN -//~ | CHAINING -//~ | CHARACTER_SET_CATALOG -//~ | CHARACTER_SET_NAME -//~ | CHARACTER_SET_SCHEMA -//~ | CHARACTERISTICS -//~ | CHARACTERS -//~ | CLASS_ORIGIN -//~ | COBOL -//~ | COLLATION -//~ | COLLATION_CATALOG -//~ | COLLATION_NAME -//~ | COLLATION_SCHEMA -//~ | COLUMNS -//~ | COLUMN_NAME -//~ | COMMAND_FUNCTION -//~ | COMMAND_FUNCTION_CODE -//~ | COMMITTED -//~ | CONDITIONAL -//~ | CONDITION_NUMBER -//~ | CONNECTION -//~ | CONNECTION_NAME -//~ | CONSTRAINT_CATALOG -//~ | CONSTRAINT_NAME -//~ | CONSTRAINT_SCHEMA -//~ | CONSTRAINTS -//~ | CONSTRUCTOR -//~ | CONTINUE -//~ | CURSOR_NAME -//~ | DATA -//~ | DATETIME_INTERVAL_CODE -//~ | DATETIME_INTERVAL_PRECISION -//~ | DEFAULTS -//~ | DEFERRABLE -//~ | DEFERRED -//~ | DEFINED -//~ | DEFINER -//~ | DEGREE -//~ | DEPTH -//~ | DERIVED -//~ | DESC -//~ | DESCRIBE_CATALOG -//~ | DESCRIBE_NAME -//~ | DESCRIBE_PROCEDURE_SPECIFIC_CATALOG -//~ | DESCRIBE_PROCEDURE_SPECIFIC_NAME -//~ | DESCRIBE_PROCEDURE_SPECIFIC_SCHEMA -//~ | DESCRIBE_SCHEMA -//~ | DESCRIPTOR -//~ | DIAGNOSTICS -//~ | DISPATCH -//~ | DOMAIN -//~ | DYNAMIC_FUNCTION -//~ | DYNAMIC_FUNCTION_CODE -//~ | ENCODING -//~ | ENFORCED -//~ | ERROR -//~ | EXCLUDE -//~ | EXCLUDING -//~ | EXPRESSION -//~ | FINAL -//~ | FINISH -//~ | FINISH_CATALOG -//~ | FINISH_NAME -//~ | FINISH_PROCEDURE_SPECIFIC_CATALOG -//~ | FINISH_PROCEDURE_SPECIFIC_NAME -//~ | FINISH_PROCEDURE_SPECIFIC_SCHEMA -//~ | FINISH_SCHEMA -//~ | FIRST -//~ | FLAG -//~ | FOLLOWING -//~ | FORMAT -//~ | FORTRAN -//~ | FOUND -//~ | FULFILL -//~ | FULFILL_CATALOG -//~ | FULFILL_NAME -//~ | FULFILL_PROCEDURE_SPECIFIC_CATALOG -//~ | FULFILL_PROCEDURE_SPECIFIC_NAME -//~ | FULFILL_PROCEDURE_SPECIFIC_SCHEMA -//~ | FULFILL_SCHEMA -//~ | G -//~ | GENERAL -//~ | GENERATED -//~ | GO -//~ | GOTO -//~ | GRANTED -//~ | HAS_PASS_THROUGH_COLUMNS -//~ | HAS_PASS_THRU_COLS -//~ | HIERARCHY -//~ | IGNORE -//~ | IMMEDIATE -//~ | IMMEDIATELY -//~ | IMPLEMENTATION -//~ | INCLUDING -//~ | INCREMENT -//~ | INITIALLY -//~ | INPUT -//~ | INSTANCE -//~ | INSTANTIABLE -//~ | INSTEAD -//~ | INVOKER -//~ | ISOLATION -//~ | IS_PRUNABLE -//~ | JSON -//~ | K -//~ | KEEP -//~ | KEY -//~ | KEYS -//~ | KEY_MEMBER -//~ | KEY_TYPE -//~ | LAST -//~ | LENGTH -//~ | LEVEL -//~ | LOCATOR -//~ | M -//~ | MAP -//~ | MATCHED -//~ | MAXVALUE -//~ | MESSAGE_LENGTH -//~ | MESSAGE_OCTET_LENGTH -//~ | MESSAGE_TEXT -//~ | MINVALUE -//~ | MORE -//~ | MUMPS -//~ | NAME -//~ | NAMES -//~ | NESTED -//~ | NESTING -//~ | NEXT -//~ | NFC -//~ | NFD -//~ | NFKC -//~ | NFKD -//~ | NORMALIZED -//~ | NULLABLE -//~ | NULLS -//~ | NUMBER -//~ | OBJECT -//~ | OCTETS -//~ | OPTION -//~ | OPTIONS -//~ | ORDERING -//~ | ORDINALITY -//~ | OTHERS -//~ | OUTPUT -//~ | OVERFLOW -//~ | OVERRIDING -//~ | P -//~ | PAD -//~ | PARAMETER_MODE -//~ | PARAMETER_NAME -//~ | PARAMETER_ORDINAL_POSITION -//~ | PARAMETER_SPECIFIC_CATALOG -//~ | PARAMETER_SPECIFIC_NAME -//~ | PARAMETER_SPECIFIC_SCHEMA -//~ | PARTIAL -//~ | PASCAL -//~ | PASS -//~ | PASSING -//~ | PAST -//~ | PATH -//~ | PLACING -//~ | PLAN -//~ | PLI -//~ | PRECEDING -//~ | PRESERVE -//~ | PRIOR -//~ | PRIVATE -//~ | PRIVATE_PARAMETERS -//~ | PRIVATE_PARAMS_S -//~ | PRIVILEGES -//~ | PRUNE -//~ | PUBLIC -//~ | QUOTES -//~ | READ -//~ | RELATIVE -//~ | REPEATABLE -//~ | RESPECT -//~ | RESTART -//~ | RESTRICT -//~ | RETURNED_CARDINALITY -//~ | RETURNED_LENGTH -//~ | RETURNED_OCTET_LENGTH -//~ | RETURNED_SQLSTATE -//~ | RETURNING -//~ | RETURNS_ONLY_PASS_THROUGH -//~ | RET_ONLY_PASS_THRU -//~ | ROLE -//~ | ROUTINE -//~ | ROUTINE_CATALOG -//~ | ROUTINE_NAME -//~ | ROUTINE_SCHEMA -//~ | ROW_COUNT -//~ | SCALAR -//~ | SCALE -//~ | SCHEMA -//~ | SCHEMA_NAME -//~ | SCOPE_CATALOG -//~ | SCOPE_NAME -//~ | SCOPE_SCHEMA -//~ | SECTION -//~ | SECURITY -//~ | SELF -//~ | SEQUENCE -//~ | SERIALIZABLE -//~ | SERVER_NAME -//~ | SESSION -//~ | SETS -//~ | SIMPLE -//~ | SIZE -//~ | SOURCE -//~ | SPACE -//~ | SPECIFIC_NAME -//~ | START_CATALOG -//~ | START_NAME -//~ | START_PROCEDURE_SPECIFIC_CATALOG -//~ | START_PROCEDURE_SPECIFIC_NAME -//~ | START_PROCEDURE_SPECIFIC_SCHEMA -//~ | START_SCHEMA -//~ | STATE -//~ | STATEMENT -//~ | STRING -//~ | STRUCTURE -//~ | STYLE -//~ | SUBCLASS_ORIGIN -//~ | T -//~ | TABLE_NAME -//~ | TABLE_SEMANTICS -//~ | TEMPORARY -//~ | THROUGH -//~ | TIES -//~ | TOP_LEVEL_COUNT -//~ | TRANSACTION -//~ | TRANSACTION_ACTIVE -//~ | TRANSACTIONS_COMMITTED -//~ | TRANSACTIONS_ROLLED_BACK -//~ | TRANSFORM -//~ | TRANSFORMS -//~ | TRIGGER_CATALOG -//~ | TRIGGER_NAME -//~ | TRIGGER_SCHEMA -//~ | TYPE -//~ | UNBOUNDED -//~ | UNCOMMITTED -//~ | UNCONDITIONAL -//~ | UNDER -//~ | UNNAMED -//~ | USAGE -//~ | USER_DEFINED_TYPE_CATALOG -//~ | USER_DEFINED_TYPE_CODE -//~ | USER_DEFINED_TYPE_NAME -//~ | USER_DEFINED_TYPE_SCHEMA -//~ | UTF16 -//~ | UTF32 -//~ | UTF8 -//~ | VIEW -//~ | WORK -//~ | WRAPPER -//~ | WRITE -//~ | ZONE - -fn parse_string_identifier(s string) !IdentifierChain { - return IdentifierChain{s} -} - -fn parse_string(s string) !string { - return s -} diff --git a/vsql/std_unique_constraint_definition.v b/vsql/std_unique_constraint_definition.v deleted file mode 100644 index b7afdc4..0000000 --- a/vsql/std_unique_constraint_definition.v +++ /dev/null @@ -1,31 +0,0 @@ -module vsql - -// ISO/IEC 9075-2:2016(E), 11.7, -// -// # Function -// -// Specify a uniqueness constraint for a table. -// -// # Format -//~ -//~ /* TableElement */ ::= -//~ -//~ -> unique_constraint_definition -//~ -//~ ::= -//~ PRIMARY KEY -> ignore -//~ -//~ /* []Identifier */ ::= -//~ - -struct UniqueConstraintDefinition { - columns []Identifier -} - -fn parse_unique_constraint_definition(columns []Identifier) !TableElement { - return UniqueConstraintDefinition{columns} -} - -fn parse_ignore() !bool { - return false -} diff --git a/vsql/std_where_clause.v b/vsql/std_where_clause.v deleted file mode 100644 index 244e337..0000000 --- a/vsql/std_where_clause.v +++ /dev/null @@ -1,17 +0,0 @@ -module vsql - -// ISO/IEC 9075-2:2016(E), 7.12, -// -// # Function -// -// Specify a table derived by the application of a to the -// result of the preceding . -// -// # Format -//~ -//~ /* BooleanValueExpression */ ::= -//~ WHERE -> where_clause - -fn parse_where_clause(b BooleanValueExpression) !BooleanValueExpression { - return b -} diff --git a/vsql/storage.v b/vsql/storage.v index 2135128..d3283bc 100644 --- a/vsql/storage.v +++ b/vsql/storage.v @@ -283,6 +283,15 @@ fn (mut f Storage) delete_row(table_name string, mut row Row) ! { } page_number := f.btree.expire(row.object_key(f.tables[table_name])!, row.tid, f.transaction_id)! + + // A negative page_number means the object didn't exist so there's nothing to + // save. This should not be possible because the delete_row will only be + // issued on a row that already exists, but I guess to be safe let's not let + // it panic. + if page_number < 0 { + return error('DELETE: integrity issue, preventing panic') + } + f.transaction_pages[page_number] = true } diff --git a/vsql/table.v b/vsql/table.v index 6fda268..8308948 100644 --- a/vsql/table.v +++ b/vsql/table.v @@ -117,9 +117,9 @@ fn new_table_from_bytes(data []u8, tid int, catalog_name string) Table { scale := b.read_i16() columns << Column{Identifier{ - catalog_name: catalog_name - schema_name: table_name.schema_name - entity_name: table_name.entity_name + catalog_name: catalog_name + schema_name: table_name.schema_name + entity_name: table_name.entity_name sub_entity_name: column_name }, type_from_number(column_type, size, scale), is_not_null} } diff --git a/vsql/time.v b/vsql/time.v index de9874d..571a831 100644 --- a/vsql/time.v +++ b/vsql/time.v @@ -65,8 +65,8 @@ fn new_timestamp_from_string(s string) !Time { expects_time_zone := s.len > 6 && (s[s.len - 6] == `+` || s[s.len - 6] == `-`) mut re := regex.regex_opt(match expects_time_zone { - true { vsql.unquoted_timestamp_with_time_zone_string } - false { vsql.unquoted_timestamp_without_time_zone_string } + true { unquoted_timestamp_with_time_zone_string } + false { unquoted_timestamp_without_time_zone_string } }) or { return error('cannot compile regex for timestamp: ${err}') } if !re.matches_string(s) { return sqlstate_42601('TIMESTAMP \'${s}\' is not valid') @@ -93,7 +93,7 @@ fn new_timestamp_from_string(s string) !Time { } mut typ := Type{ - typ: if expects_time_zone { + typ: if expects_time_zone { .is_timestamp_with_time_zone } else { .is_timestamp_without_time_zone @@ -142,12 +142,12 @@ fn new_date_from_string(s string) !Time { fn new_time_from_components(typ Type, year int, month int, day int, hour int, minute int, second int, microsecond int, time_zone i16) Time { return Time{typ, time_zone, time.new(time.Time{ - year: year - month: month - day: day - hour: hour - minute: minute - second: second + year: year + month: month + day: day + hour: hour + minute: minute + second: second nanosecond: microsecond * 1000 })} } @@ -156,23 +156,23 @@ fn new_time_from_bytes(typ Type, bytes []u8) Time { mut buf := new_bytes(bytes) mut ts_i64 := buf.read_i64() - year := int(ts_i64 / vsql.year_period) - ts_i64 -= year * vsql.year_period + year := int(ts_i64 / year_period) + ts_i64 -= year * year_period - month := int(ts_i64 / vsql.month_period) - ts_i64 -= month * vsql.month_period + month := int(ts_i64 / month_period) + ts_i64 -= month * month_period - day := int(ts_i64 / vsql.day_period) - ts_i64 -= day * vsql.day_period + day := int(ts_i64 / day_period) + ts_i64 -= day * day_period - hour := int(ts_i64 / vsql.hour_period) - ts_i64 -= hour * vsql.hour_period + hour := int(ts_i64 / hour_period) + ts_i64 -= hour * hour_period - minute := int(ts_i64 / vsql.minute_period) - ts_i64 -= minute * vsql.minute_period + minute := int(ts_i64 / minute_period) + ts_i64 -= minute * minute_period - second := int(ts_i64 / vsql.second_period) - ts_i64 -= second * vsql.second_period + second := int(ts_i64 / second_period) + ts_i64 -= second * second_period mut time_zone := i16(0) if typ.typ == .is_time_with_time_zone || typ.typ == .is_timestamp_with_time_zone { @@ -230,13 +230,13 @@ fn (t Time) i64() i64 { // See i64() for details. fn (t Time) time_i64() i64 { - return t.t.hour * vsql.hour_period + t.t.minute * vsql.minute_period + - t.t.second * vsql.second_period + int(t.t.nanosecond / 1000) + return t.t.hour * hour_period + t.t.minute * minute_period + t.t.second * second_period + + int(t.t.nanosecond / 1000) } // See i64() for details. fn (t Time) date_i64() i64 { - return t.t.year * vsql.year_period + t.t.month * vsql.month_period + t.t.day * vsql.day_period + return t.t.year * year_period + t.t.month * month_period + t.t.day * day_period } // Returns the Time formatted based on its type. diff --git a/vsql/type.v b/vsql/type.v index b75ccb1..f6f6af9 100644 --- a/vsql/type.v +++ b/vsql/type.v @@ -17,21 +17,21 @@ mut: // Represents the fundamental SQL type. enum SQLType { - is_bigint // BIGINT - is_boolean // BOOLEAN - is_character // CHARACTER(n), CHAR(n), CHARACTER and CHAR - is_double_precision // DOUBLE PRECISION, FLOAT and FLOAT(n) - is_integer // INTEGER and INT - is_real // REAL - is_smallint // SMALLINT - is_varchar // CHARACTER VARYING, CHAR VARYING and VARCHAR - is_date // DATE - is_time_without_time_zone // TIME, TIME WITHOUT TIME ZONE - is_time_with_time_zone // TIME WITH TIME ZONE + is_bigint // BIGINT + is_boolean // BOOLEAN + is_character // CHARACTER(n), CHAR(n), CHARACTER and CHAR + is_double_precision // DOUBLE PRECISION, FLOAT and FLOAT(n) + is_integer // INTEGER and INT + is_real // REAL + is_smallint // SMALLINT + is_varchar // CHARACTER VARYING, CHAR VARYING and VARCHAR + is_date // DATE + is_time_without_time_zone // TIME, TIME WITHOUT TIME ZONE + is_time_with_time_zone // TIME WITH TIME ZONE is_timestamp_without_time_zone // TIMESTAMP, TIMESTAMP WITHOUT TIME ZONE - is_timestamp_with_time_zone // TIMESTAMP WITH TIME ZONE - is_decimal // DECIMAL - is_numeric // NUMERIC + is_timestamp_with_time_zone // TIMESTAMP WITH TIME ZONE + is_decimal // DECIMAL + is_numeric // NUMERIC } // The SQL representation, such as ``TIME WITHOUT TIME ZONE``. diff --git a/vsql/utils.v b/vsql/utils.v index 593f28c..fce519c 100644 --- a/vsql/utils.v +++ b/vsql/utils.v @@ -41,3 +41,28 @@ fn left_pad(s string, c string, len int) string { return new_s } + +fn append_list[T](list []T, element T) []T { + mut new_list := list.clone() + new_list << element + return new_list +} + +fn push_list[T](element T, list []T) []T { + mut new_list := [element] + for e in list { + new_list << e + } + + return new_list +} + +fn merge_maps[K, V](a map[K]V, b map[K]V) map[K]V { + mut new_map := a.clone() + + for k, v in b { + new_map[k] = v + } + + return new_map +} diff --git a/vsql/value.v b/vsql/value.v index 0295d9f..1acfe24 100644 --- a/vsql/value.v +++ b/vsql/value.v @@ -38,10 +38,10 @@ pub mut: fn (e Value) compile(mut c Compiler) !CompileResult { return CompileResult{ - run: fn [e] (mut conn Connection, data Row, params map[string]Value) !Value { + run: fn [e] (mut conn Connection, data Row, params map[string]Value) !Value { return e } - typ: e.typ + typ: e.typ contains_agg: false } } @@ -70,7 +70,7 @@ mut: // values need to have a type. pub fn new_null_value(typ SQLType) Value { return Value{ - typ: Type{typ, 0, 0, false} + typ: Type{typ, 0, 0, false} is_null: true } } @@ -80,7 +80,7 @@ pub fn new_null_value(typ SQLType) Value { pub fn new_boolean_value(b bool) Value { return Value{ typ: Type{.is_boolean, 0, 0, false} - v: InternalValue{ + v: InternalValue{ bool_value: if b { .is_true } else { .is_false } } } @@ -90,7 +90,7 @@ pub fn new_boolean_value(b bool) Value { // representation of ``BOOLEAN``. pub fn new_unknown_value() Value { return Value{ - typ: Type{.is_boolean, 0, 0, false} + typ: Type{.is_boolean, 0, 0, false} is_null: true } } @@ -99,7 +99,7 @@ pub fn new_unknown_value() Value { pub fn new_double_precision_value(x f64) Value { return Value{ typ: Type{.is_double_precision, 0, 0, false} - v: InternalValue{ + v: InternalValue{ f64_value: x } } @@ -109,7 +109,7 @@ pub fn new_double_precision_value(x f64) Value { pub fn new_integer_value(x int) Value { return Value{ typ: Type{.is_integer, 0, 0, false} - v: InternalValue{ + v: InternalValue{ int_value: x } } @@ -119,7 +119,7 @@ pub fn new_integer_value(x int) Value { pub fn new_bigint_value(x i64) Value { return Value{ typ: Type{.is_bigint, 0, 0, false} - v: InternalValue{ + v: InternalValue{ int_value: x } } @@ -129,7 +129,7 @@ pub fn new_bigint_value(x i64) Value { pub fn new_real_value(x f32) Value { return Value{ typ: Type{.is_real, 0, 0, false} - v: InternalValue{ + v: InternalValue{ f64_value: x } } @@ -139,7 +139,7 @@ pub fn new_real_value(x f32) Value { pub fn new_smallint_value(x i16) Value { return Value{ typ: Type{.is_smallint, 0, 0, false} - v: InternalValue{ + v: InternalValue{ int_value: x } } @@ -149,7 +149,7 @@ pub fn new_smallint_value(x i16) Value { pub fn new_varchar_value(x string) Value { return Value{ typ: Type{.is_varchar, x.len, 0, false} - v: InternalValue{ + v: InternalValue{ string_value: x } } @@ -160,7 +160,7 @@ pub fn new_varchar_value(x string) Value { pub fn new_character_value(x string) Value { return Value{ typ: Type{.is_character, x.len, 0, false} - v: InternalValue{ + v: InternalValue{ string_value: x } } @@ -180,7 +180,7 @@ pub fn new_numeric_value(x string) Value { return Value{ typ: n.typ - v: InternalValue{ + v: InternalValue{ numeric_value: n.normalize_denominator(n.typ) } } @@ -201,7 +201,7 @@ pub fn new_decimal_value(x string) Value { return Value{ typ: typ - v: InternalValue{ + v: InternalValue{ numeric_value: n.normalize_denominator(typ) } } @@ -210,7 +210,7 @@ pub fn new_decimal_value(x string) Value { fn new_numeric_value_from_numeric(n Numeric) Value { return Value{ typ: n.typ - v: InternalValue{ + v: InternalValue{ numeric_value: n.normalize_denominator(n.typ) } } @@ -221,7 +221,7 @@ fn new_decimal_value_from_numeric(n Numeric) Value { return Value{ typ: typ - v: InternalValue{ + v: InternalValue{ numeric_value: n.normalize_denominator(n.typ) } } @@ -233,7 +233,7 @@ pub fn new_timestamp_value(ts string) !Value { return Value{ typ: t.typ - v: InternalValue{ + v: InternalValue{ time_value: t } } @@ -245,7 +245,7 @@ pub fn new_time_value(ts string) !Value { return Value{ typ: t.typ - v: InternalValue{ + v: InternalValue{ time_value: t } } @@ -257,7 +257,7 @@ pub fn new_date_value(ts string) !Value { return Value{ typ: t.typ - v: InternalValue{ + v: InternalValue{ time_value: t } } diff --git a/vsql/virtual_table.v b/vsql/virtual_table.v index 0bdd9d2..1dc30d2 100644 --- a/vsql/virtual_table.v +++ b/vsql/virtual_table.v @@ -35,8 +35,8 @@ pub fn (mut v VirtualTable) done() { pub fn (v VirtualTable) table() Table { return Table{ - name: v.create_table_stmt.table_name - columns: v.create_table_stmt.columns() + name: v.create_table_stmt.table_name + columns: v.create_table_stmt.columns() is_virtual: true } } diff --git a/vsql/walk.v b/vsql/walk.v index 34998b5..b1f8995 100644 --- a/vsql/walk.v +++ b/vsql/walk.v @@ -127,7 +127,7 @@ fn (o &PrimaryKeyOperation) columns() Columns { fn (mut o PrimaryKeyOperation) execute(_ []Row) ![]Row { mut c := Compiler{ - conn: o.conn + conn: o.conn params: o.params } mut lower := o.lower.compile(mut c)!.run(mut o.conn, Row{}, o.params)!