diff --git a/edb/lang/edgeql/ast.py b/edb/lang/edgeql/ast.py index 519b9c66f9..0c6c012af9 100644 --- a/edb/lang/edgeql/ast.py +++ b/edb/lang/edgeql/ast.py @@ -24,6 +24,7 @@ from edb.lang.common import ast, parsing from . import functypes as ft +from . import quote # Operators @@ -235,6 +236,25 @@ class Constant(Expr): value: typing.Union[int, str, float, bool, bytes, decimal.Decimal] +class StringConstant(Constant): + quote: str + + @classmethod + def from_pystr(cls, s: str): + s = s.replace('\\', '\\\\') + value = quote.quote_literal(s) + return cls(value=value[1:-1], quote="'") + + +class RawStringConstant(Constant): + quote: str + + @classmethod + def from_pystr(cls, s: str): + value = quote.quote_literal(s) + return cls(value=value[1:-1], quote="'") + + class Parameter(Expr): name: str diff --git a/edb/lang/edgeql/codegen.py b/edb/lang/edgeql/codegen.py index b77d9c85d4..8e2daf5ac3 100644 --- a/edb/lang/edgeql/codegen.py +++ b/edb/lang/edgeql/codegen.py @@ -505,6 +505,15 @@ def visit_ShapeElement(self, node): def visit_Parameter(self, node): self.write(param_to_str(node.name)) + def visit_StringConstant(self, node): + self.write(node.quote, node.value, node.quote) + + def visit_RawStringConstant(self, node): + if node.quote.startswith('$'): + self.write(node.quote, node.value, node.quote) + else: + self.write('r', node.quote, node.value, node.quote) + def visit_Constant(self, node): if isinstance(node.value, str): self.write(edgeql_quote.quote_literal(node.value)) diff --git a/edb/lang/edgeql/compiler/decompiler.py b/edb/lang/edgeql/compiler/decompiler.py index 0d25de255a..a0cc86b246 100644 --- a/edb/lang/edgeql/compiler/decompiler.py +++ b/edb/lang/edgeql/compiler/decompiler.py @@ -175,6 +175,12 @@ def visit_Parameter(self, node): def visit_Constant(self, node): return qlast.Constant(value=node.value) + def visit_StringConstant(self, node): + return qlast.StringConstant.from_pystr(node.value) + + def visit_RawStringConstant(self, node): + return qlast.RawStringConstant.from_pystr(node.value) + def visit_Array(self, node): return qlast.Array(elements=[ self.visit(e) for e in node.elements diff --git a/edb/lang/edgeql/compiler/expr.py b/edb/lang/edgeql/compiler/expr.py index 05e61b2512..5b35acdfa3 100644 --- a/edb/lang/edgeql/compiler/expr.py +++ b/edb/lang/edgeql/compiler/expr.py @@ -161,11 +161,20 @@ def compile_Set( def compile_Constant( expr: qlast.Base, *, ctx: context.ContextLevel) -> irast.Base: + node_cls = irast.Constant + if expr.value is None: ct = None else: - if isinstance(expr.value, str): + if isinstance(expr, qlast.StringConstant): + std_type = 'std::str' + node_cls = irast.StringConstant + elif isinstance(expr, qlast.RawStringConstant): + std_type = 'std::str' + node_cls = irast.RawStringConstant + elif isinstance(expr.value, str): std_type = 'std::str' + node_cls = irast.StringConstant elif isinstance(expr.value, decimal.Decimal): std_type = 'std::decimal' elif isinstance(expr.value, float): @@ -187,7 +196,7 @@ def compile_Constant( ct = ctx.schema.get(std_type) return setgen.generated_set( - irast.Constant(value=expr.value, type=ct), ctx=ctx) + node_cls(value=expr.value, type=ct), ctx=ctx) @dispatch.compile.register(qlast.EmptyCollection) diff --git a/edb/lang/edgeql/parser/grammar/ddl.py b/edb/lang/edgeql/parser/grammar/ddl.py index 3a773deb44..6e3d3bf07a 100644 --- a/edb/lang/edgeql/parser/grammar/ddl.py +++ b/edb/lang/edgeql/parser/grammar/ddl.py @@ -29,7 +29,7 @@ from ...errors import EdgeQLSyntaxError -from .expressions import Nonterm, BaseStringConstant +from .expressions import Nonterm from . import tokens from .precedence import * # NOQA @@ -400,9 +400,11 @@ class OptDeltaTarget(Nonterm): def reduce_empty(self): self.val = None - def reduce_TO_AnyIdentifier_SCONST(self, *kids): + def reduce_TO_AnyIdentifier_BaseStringConstant(self, *kids): self.val = [kids[1], kids[2]] + def reduce_TO_AnyIdentifier_BaseRawStringConstant(self, *kids): + self.val = [kids[1], kids[2]] # # DELTAS @@ -411,15 +413,17 @@ def reduce_TO_AnyIdentifier_SCONST(self, *kids): # # CREATE MIGRATION # + + class CreateDeltaStmt(Nonterm): - def _parse_schema_decl(self, tok: tokens.T_SCONST): + def _parse_schema_decl(self, tok): from edb.lang.common.exceptions import get_context from edb.lang.schema import parser ctx = tok.context try: - node = parser.parse(BaseStringConstant.parse_body(tok)) + node = parser.parse(tok.val.value) except parsing.ParserError as err: context.rebase_context( ctx, get_context(err, parsing.ParserContext)) @@ -1638,14 +1642,26 @@ def _parse_language(node): class FromFunction(Nonterm): - def reduce_FROM_Identifier_SCONST(self, *kids): + def reduce_FROM_Identifier_BaseStringConstant(self, *kids): lang = _parse_language(kids[1]) + code = kids[2].val.value + self.val = qlast.FunctionCode(language=lang, code=code) - # we need literal value of the string - code = BaseStringConstant.parse_body(kids[2]) - + def reduce_FROM_Identifier_BaseRawStringConstant(self, *kids): + lang = _parse_language(kids[1]) + code = kids[2].val.value self.val = qlast.FunctionCode(language=lang, code=code) + def reduce_FROM_Identifier_FUNCTION_BaseRawStringConstant(self, *kids): + lang = _parse_language(kids[1]) + if lang != qlast.Language.SQL: + raise EdgeQLSyntaxError( + f'{lang} language is not supported in FROM FUNCTION clause', + context=kids[1].context) from None + + self.val = qlast.FunctionCode(language=lang, + from_name=kids[3].val.value) + def reduce_FROM_Identifier_FUNCTION_BaseStringConstant(self, *kids): lang = _parse_language(kids[1]) if lang != qlast.Language.SQL: diff --git a/edb/lang/edgeql/parser/grammar/expressions.py b/edb/lang/edgeql/parser/grammar/expressions.py index 45492a4450..3a4b42e81d 100644 --- a/edb/lang/edgeql/parser/grammar/expressions.py +++ b/edb/lang/edgeql/parser/grammar/expressions.py @@ -30,7 +30,7 @@ from ...errors import EdgeQLSyntaxError -from . import keywords, precedence, tokens +from . import keywords, precedence, tokens, lexer from .precedence import * # NOQA from .tokens import * # NOQA @@ -823,6 +823,7 @@ class Constant(Nonterm): # ArgConstant # | BaseNumberConstant # | BaseStringConstant + # | BaseRawStringConstant # | BaseBooleanConstant # | BaseBytesConstant @@ -832,6 +833,9 @@ def reduce_ArgConstant(self, *kids): def reduce_BaseNumberConstant(self, *kids): self.val = kids[0].val + def reduce_BaseRawStringConstant(self, *kids): + self.val = kids[0].val + def reduce_BaseStringConstant(self, *kids): self.val = kids[0].val @@ -863,22 +867,27 @@ class BaseStringConstant(Nonterm): valid_str_re = re.compile(r''' ^ (?P - ( - ' | " | \$([A-Za-z\200-\377_][0-9]*)*\$ - ) + ' | " ) (?P (?: + \\\n | # line continuation \n | # new line \\\\ | # \\ \\['"] | # \' or \" - \\x[0-9a-fA-F]{2} | # \x00 -- hex code - \\u[0-9a-fA-F]{4} | # \u0000 - \\U[0-9a-fA-F]{8} | # \U00000000 - \\ (t | n | r) | # \t, \n, or \r + # + \\x[0-7][0-9a-fA-F] | # \xhh -- hex code, up to 0x7F + # (higher values are not permitted + # because it is ambiguous whether + # they mean Unicode code points or + # byte values.) + # + \\u[0-9a-fA-F]{4} | # \uhhhh + \\U[0-9a-fA-F]{8} | # \Uhhhhhhhh + \\ (?: t | n | r) | # \t, \n, or \r [^\\] | # anything except \ - (?P # capture any invalid \escape + (?P # capture any invalid \escape sequence \\x.{1,2} | \\u.{1,4} | \\U.{1,8} | @@ -890,24 +899,60 @@ class BaseStringConstant(Nonterm): $ ''', re.X) - @classmethod - def parse_body(cls, str_tok: tokens.T_SCONST): - match = cls.valid_str_re.match(str_tok.val) + def reduce_SCONST(self, str_tok): + match = self.valid_str_re.match(str_tok.val) if not match: raise EdgeQLSyntaxError( - f"invalid str literal", context=str_tok.context) + f"invalid string literal", context=str_tok.context) if match.group('err_esc'): raise EdgeQLSyntaxError( - f"invalid str literal: invalid escape sequence " + f"invalid string literal: invalid escape sequence " f"'{match.group('err_esc')}'", context=str_tok.context) - return match.group('body') + quote = match.group('Q') + val = match.group('body') + + # handle line continuations + val = re.sub(r'\\\n', '', val) - def reduce_SCONST(self, str_tok): - val = self.parse_body(str_tok) - self.val = qlast.Constant(value=val) + self.val = qlast.StringConstant(value=val, quote=quote) + + +class BaseRawStringConstant(Nonterm): + + valid_rstr_re = re.compile(rf''' + ^ + (?: + r + )? + (?P + (?: + (?<=r) (?: ' | ") + ) | (?: + (? + (?: + \n | . + )*? + ) + (?P=Q) + $ + ''', re.X) + + def reduce_RSCONST(self, str_tok): + match = self.valid_rstr_re.match(str_tok.val) + if not match: + raise EdgeQLSyntaxError( + f"invalid raw string literal", context=str_tok.context) + + quote = match.group('Q') + val = match.group('body') + + self.val = qlast.RawStringConstant(value=val, quote=quote) class BaseBytesConstant(Nonterm): @@ -918,20 +963,18 @@ class BaseBytesConstant(Nonterm): b ) (?P - ( - ' | " - ) + ' | " ) (?P ( \n | # new line \\\\ | # \\ \\['"] | # \' or \" - \\x[0-9a-fA-F]{2} | # \x00 -- hex code - \\ (t | n | r) | # \t, \n, or \r + \\x[0-9a-fA-F]{2} | # \xhh -- hex code + \\ (?: t | n | r) | # \t, \n, or \r [\x20-\x5b\x5d-\x7e] | # match any printable ASCII, except '\' - (?P # capture any invalid \escape + (?P # capture any invalid \escape sequence \\x.{1,2} | \\. ) | diff --git a/edb/lang/edgeql/parser/grammar/lexer.py b/edb/lang/edgeql/parser/grammar/lexer.py index eb2bb893f5..fbb346a9b2 100644 --- a/edb/lang/edgeql/parser/grammar/lexer.py +++ b/edb/lang/edgeql/parser/grammar/lexer.py @@ -43,7 +43,7 @@ class EdgeQLLexer(lexer.Lexer): MERGE_TOKENS = {('NAMED', 'ONLY')} NL = 'NL' - MULTILINE_TOKENS = frozenset(('SCONST', 'BCONST')) + MULTILINE_TOKENS = frozenset(('SCONST', 'BCONST', 'RSCONST')) RE_FLAGS = re.X | re.M | re.I # Basic keywords @@ -122,31 +122,50 @@ class EdgeQLLexer(lexer.Lexer): b ) (?P + ' | " + ) + (?: ( - ' | " + \\\\ | \\['"] | \n | . + # we'll validate escape codes in the parser + )*? + ) + (?P=BQ) + '''), + + Rule(token='RSCONST', + next_state=STATE_KEEP, + regexp=rf''' + (?: + r + )? + (?P + (?: + (?<=r) (?: ' | ") + ) | (?: + (? - ( - ' | " | - {re_dquote} - ) + ' | " ) (?: - (\\\\ | \\['"] | \n | .)*? - # we'll validate escapes codes in the parser + ( + \\\\ | \\['"] | \n | . + # we'll validate escape codes in the parser + )*? ) (?P=Q) '''), diff --git a/edb/lang/edgeql/parser/grammar/tokens.py b/edb/lang/edgeql/parser/grammar/tokens.py index 9eb93bf104..8e0e43275a 100644 --- a/edb/lang/edgeql/parser/grammar/tokens.py +++ b/edb/lang/edgeql/parser/grammar/tokens.py @@ -177,6 +177,10 @@ class T_SCONST(Token): pass +class T_RSCONST(Token): + pass + + class T_IDENT(Token): pass diff --git a/edb/lang/graphql/translator.py b/edb/lang/graphql/translator.py index 1e96d06d9c..47c87be643 100644 --- a/edb/lang/graphql/translator.py +++ b/edb/lang/graphql/translator.py @@ -119,8 +119,8 @@ def visit_Document(self, node): if (isinstance(el.compexpr, qlast.FunctionCall) and el.compexpr.func == 'str_to_json'): name = el.expr.steps[0].ptr.name - el.compexpr.args[0].arg.value = json.dumps( - gqlresult.data[name], indent=4).replace('\\', '\\\\') + el.compexpr.args[0].arg = qlast.StringConstant.from_pystr( + json.dumps(gqlresult.data[name], indent=4)) return translated diff --git a/edb/lang/ir/ast.py b/edb/lang/ir/ast.py index f7b9be370a..565716747f 100644 --- a/edb/lang/ir/ast.py +++ b/edb/lang/ir/ast.py @@ -146,6 +146,14 @@ def __init__(self, *args, type, **kwargs): super().__init__(*args, type=type, **kwargs) +class StringConstant(Constant): + pass + + +class RawStringConstant(Constant): + pass + + class Parameter(Base): name: str diff --git a/edb/lang/schema/_graphql.eql b/edb/lang/schema/_graphql.eql index e90acaf356..e8dcadfb66 100644 --- a/edb/lang/schema/_graphql.eql +++ b/edb/lang/schema/_graphql.eql @@ -19,7 +19,7 @@ CREATE FUNCTION graphql::short_name(name: std::str) -> std::str FROM EdgeQL $$ - SELECT re_replace(name, '.+?::(.+$)', '\\1') + 'Type' + SELECT re_replace(name, '.+?::(.+$)', r'\1') + 'Type' $$; # create Query diff --git a/edb/server/pgsql/ast.py b/edb/server/pgsql/ast.py index 2c50e1459a..63af2ca52f 100644 --- a/edb/server/pgsql/ast.py +++ b/edb/server/pgsql/ast.py @@ -440,6 +440,12 @@ class Constant(BaseExpr): val: object +class EscapedStringConstant(Constant): + """An "E"-prefixed string.""" + + val: str + + class LiteralExpr(BaseExpr): """A literal expression.""" diff --git a/edb/server/pgsql/codegen.py b/edb/server/pgsql/codegen.py index 6e0174d1ae..0f37b8d7ea 100644 --- a/edb/server/pgsql/codegen.py +++ b/edb/server/pgsql/codegen.py @@ -517,11 +517,15 @@ def visit_Constant(self, node): elif isinstance(node.val, bytes): b = binascii.b2a_hex(node.val).decode('ascii') self.write(f"'\\x{b}'::bytea") - elif isinstance(node.val, str): - self.write(common.quote_e_literal(node.val)) else: self.write(common.quote_literal(str(node.val))) + def visit_EscapedStringConstant(self, node): + if node.val is None: + self.write('NULL') + else: + self.write(common.quote_e_literal(node.val)) + def visit_ParamRef(self, node): self.write('$', str(node.number)) diff --git a/edb/server/pgsql/compiler/expr.py b/edb/server/pgsql/compiler/expr.py index 29559bb89c..685522fd34 100644 --- a/edb/server/pgsql/compiler/expr.py +++ b/edb/server/pgsql/compiler/expr.py @@ -131,7 +131,12 @@ def compile_Parameter( @dispatch.compile.register(irast.Constant) def compile_Constant( expr: irast.Base, *, ctx: context.CompilerContextLevel) -> pgast.Base: - result = pgast.Constant(val=expr.value) + + if isinstance(expr, irast.StringConstant): + result = pgast.EscapedStringConstant(val=expr.value) + else: + result = pgast.Constant(val=expr.value) + result = typecomp.cast( result, source_type=expr.type, target_type=expr.type, ir_expr=expr, force=True, env=ctx.env) diff --git a/tests/schemas/constraints.eschema b/tests/schemas/constraints.eschema index 63ba82da71..6f98d3e240 100644 --- a/tests/schemas/constraints.eschema +++ b/tests/schemas/constraints.eschema @@ -37,11 +37,11 @@ scalar type constraint_minmax extending str: scalar type constraint_strvalue extending str: constraint expression on (__subject__[-1:] = '9') - constraint regexp("^\\d+$") + constraint regexp(r"^\d+$") constraint expression on (__subject__[0] = '9') - constraint regexp("^\\d+9{3,}.*$") + constraint regexp(r"^\d+9{3,}.*$") # A variant of enum that uses an array argument instead of diff --git a/tests/schemas/constraints_migration/schema.eschema b/tests/schemas/constraints_migration/schema.eschema index 129e60f3b9..582f6471de 100644 --- a/tests/schemas/constraints_migration/schema.eschema +++ b/tests/schemas/constraints_migration/schema.eschema @@ -37,11 +37,11 @@ scalar type constraint_minmax extending str: scalar type constraint_strvalue extending str: constraint expression on (__subject__[-1:] = '9') - constraint regexp("^\\d+$") + constraint regexp(r"^\d+$") constraint expression on (__subject__[0] = '9') - constraint regexp("^\\d+9{3,}.*$") + constraint regexp(r"^\d+9{3,}.*$") scalar type constraint_enum extending str: diff --git a/tests/schemas/constraints_migration/updated_schema.eschema b/tests/schemas/constraints_migration/updated_schema.eschema index 402167b122..36c7eb79c4 100644 --- a/tests/schemas/constraints_migration/updated_schema.eschema +++ b/tests/schemas/constraints_migration/updated_schema.eschema @@ -37,11 +37,11 @@ scalar type constraint_minmax extending str: scalar type constraint_strvalue extending str: constraint expression on (__subject__[-1:] = '9') - constraint regexp("^\\d+$") + constraint regexp(r"^\d+$") constraint expression on (__subject__[0] = '9') - constraint regexp("^\\d+9{3,}.*$") + constraint regexp(r"^\d+9{3,}.*$") scalar type constraint_enum extending str: diff --git a/tests/test_constraints.py b/tests/test_constraints.py index abe4ba9941..d9663d1ea9 100644 --- a/tests/test_constraints.py +++ b/tests/test_constraints.py @@ -83,12 +83,12 @@ async def test_constraints_scalar_length(self): async def test_constraints_scalar_minmax(self): data = { # max-value is "9999999989" - (10 ** 9 - 1, "Maximum allowed value for .* is '9999999989'."), + (10 ** 9 - 1, 'Maximum allowed value for .* is "9999999989".'), (10 ** 9 - 11, 'good'), # min-value is "99990000" (10 ** 8 - 10 ** 4 - 1, - "Minimum allowed value for .* is '99990000'."), + 'Minimum allowed value for .* is "99990000".'), (10 ** 8 - 21, 'good'), } diff --git a/tests/test_edgeql_expressions.py b/tests/test_edgeql_expressions.py index 52dc067e23..1f21e4ddcc 100644 --- a/tests/test_edgeql_expressions.py +++ b/tests/test_edgeql_expressions.py @@ -1141,10 +1141,26 @@ async def test_edgeql_expr_string_08(self): SELECT ':\x62:\u2665:\U000025C6:☎️:'; SELECT '\'"\\\'\""\\x\\u'; SELECT "'\"\\\'\"\\x\\u"; + + SELECT 'aa\ + bb \ + aa'; + + SELECT r'\n'; + + SELECT r'aa\ + bb \ + aa'; """, [ [':b:♥:◆:☎️:'], ['\'"\\\'\""\\x\\u'], ['\'"\\\'"\\x\\u'], + + ['aa bb aa'], + + ['\\n'], + + ['aa\\\n bb \\\n aa'], ]) async def test_edgeql_expr_tuple_01(self): diff --git a/tests/test_edgeql_syntax.py b/tests/test_edgeql_syntax.py index 02d67b376e..d22facb014 100644 --- a/tests/test_edgeql_syntax.py +++ b/tests/test_edgeql_syntax.py @@ -100,15 +100,19 @@ def test_edgeql_syntax_constants_02(self): """ SELECT 'a1'; SELECT "a1"; + SELECT r'a1'; + SELECT r"a1"; SELECT $$a1$$; SELECT $qwe$a1$qwe$; % OK % SELECT 'a1'; - SELECT 'a1'; - SELECT 'a1'; - SELECT 'a1'; + SELECT "a1"; + SELECT r'a1'; + SELECT r"a1"; + SELECT $$a1$$; + SELECT $qwe$a1$qwe$; """ def test_edgeql_syntax_constants_03(self): @@ -214,7 +218,7 @@ def test_edgeql_syntax_constants_14(self): """ @tb.must_fail(errors.EdgeQLSyntaxError, - r"invalid str literal: invalid escape sequence '\\c'", + r"invalid string literal: invalid escape sequence '\\c'", line=2, col=16) def test_edgeql_syntax_constants_15(self): """ @@ -222,7 +226,7 @@ def test_edgeql_syntax_constants_15(self): """ @tb.must_fail(errors.EdgeQLSyntaxError, - r"invalid str literal: invalid escape sequence '\\x0z'", + r"invalid string literal: invalid escape sequence '\\x0z'", line=2, col=16) def test_edgeql_syntax_constants_16(self): r""" @@ -244,7 +248,7 @@ def test_edgeql_syntax_constants_18(self): """ @tb.must_fail(errors.EdgeQLSyntaxError, - r"invalid str literal: invalid escape sequence '\\u0zaa'", + r"invalid string literal: invalid escape sequence '\\u0zaa'", line=2, col=16) def test_edgeql_syntax_constants_19(self): r""" @@ -261,7 +265,7 @@ def test_edgeql_syntax_constants_20(self): def test_edgeql_syntax_constants_21(self): r""" - select '\'"\\\'\""\\x\\u'; + SELECT '\'"\\\'\""\\x\\u'; """ def test_edgeql_syntax_constants_22(self): @@ -331,6 +335,78 @@ def test_edgeql_syntax_constants_30(self): SELECT 'aaa\x0'; """ + def test_edgeql_syntax_constants_31(self): + r""" + SELECT 'aa\ + bb \ + aa'; +% OK % + SELECT 'aa bb aa'; + """ + + @tb.must_fail(errors.EdgeQLSyntaxError, + r"invalid string literal", + line=2, col=16) + def test_edgeql_syntax_constants_32(self): + r""" + SELECT 'aa\ + bb \ + aa\'; +% OK % + SELECT 'aabb aa'; + """ + + def test_edgeql_syntax_constants_33(self): + r""" + SELECT r'aaa\x0'; + """ + + def test_edgeql_syntax_constants_34(self): + r""" + SELECT r'\'; + """ + + def test_edgeql_syntax_constants_35(self): + r""" + SELECT r"\n\w\d"; + """ + + def test_edgeql_syntax_constants_36(self): + r""" + SELECT $aa$\n\w\d$aa$; + """ + + def test_edgeql_syntax_constants_37(self): + r""" + SELECT "'''"; + """ + + def test_edgeql_syntax_constants_38(self): + r""" + SELECT "\n"; + """ + + def test_edgeql_syntax_constants_39(self): + r""" + SELECT "\x1F\x01\x00\x6e"; + """ + + @tb.must_fail(errors.EdgeQLSyntaxError, + r"invalid escape sequence '\\x8F'", + line=2, col=16) + def test_edgeql_syntax_constants_40(self): + r""" + SELECT "\x1F\x01\x8F\x6e"; + """ + + @tb.must_fail(errors.EdgeQLSyntaxError, + r"invalid string literal: invalid escape sequence '\\\('", + line=2, col=16) + def test_edgeql_syntax_constants_41(self): + """ + SELECT 'aaa \(aaa) bbb'; + """ + @tb.must_fail(errors.EdgeQLSyntaxError, line=1, col=12) def test_edgeql_syntax_ops_01(self): """SELECT 40 >> 2;""" diff --git a/tests/test_schema_syntax.py b/tests/test_schema_syntax.py index 03954ed6c6..49031fa008 100644 --- a/tests/test_schema_syntax.py +++ b/tests/test_schema_syntax.py @@ -715,7 +715,7 @@ def test_eschema_syntax_link_09(self): abstract link time_estimate: property unit -> str: - constraint my_constraint(')', `)`(')')) + constraint my_constraint(')', `)`($$)$$)) """ def test_eschema_syntax_link_10(self): @@ -825,7 +825,7 @@ def test_eschema_syntax_function_02(self): function some_func(foo: std::int64 = 42) -> std::str: from sql := - 'SELECT \'life\';' + "SELECT 'life';" """ def test_eschema_syntax_function_03(self): @@ -904,7 +904,7 @@ def test_eschema_syntax_function_09(self): % OK % - function some_func(foo: str = ')') -> std::str: + function some_func(foo: str = $$)$$) -> std::str: from edgeql function: some_other_func """ @@ -915,7 +915,7 @@ def test_eschema_syntax_function_10(self): % OK % - function some_func(foo: str = ')') -> std::str: + function some_func(foo: str = $a1$)$a1$) -> std::str: from edgeql function: some_other_func """