diff --git a/common/concertina_lib.py b/common/concertina_lib.py
index 949f307..33b22de 100644
--- a/common/concertina_lib.py
+++ b/common/concertina_lib.py
@@ -145,6 +145,8 @@ def UnderstandIterations(self):
def __init__(self, config, engine, display_mode='colab', iterations=None):
self.config = config
+ self.recent_display_update_seconds = 0
+ self.display_update_period = 0.0000000001
self.iterations = iterations or {}
self.action_iteration = None
self.iteration_repetitions = None
@@ -197,7 +199,7 @@ def RunOneAction(self):
def Run(self):
while self.actions_to_run:
self.RunOneAction()
- self.UpdateDisplay()
+ self.UpdateDisplay(final=True)
def ActionColor(self, a):
if self.action[a].get('type') == 'data':
@@ -258,9 +260,12 @@ def ColoredNode(node):
else:
assert False, self.display_mode
elif node in self.complete_actions and self.display_mode == 'colab-text' and self.actions_to_run:
+ if node not in self.engine.completion_time:
+ suffix = ' (input data)'
+ else:
+ suffix = ' (%d ms)' % self.engine.completion_time[node]
return (
- '' + node +
- ' (%d ms)' % self.engine.completion_time[node] + ''
+ '' + node + suffix + ''
)
else:
if node in self.complete_actions:
@@ -305,7 +310,7 @@ def ProgressBar(self):
'.' * (30 - (complete_work * 30 // total_work)) +
']' + ' %.2f%% complete.' % percent_complete)
if total_work == complete_work:
- progress_bar = '[' + 'Execution complete.'.center(30, ' ') + ']'
+ progress_bar = '[' + 'Execution complete.'.center(30, ' ') + ']' + ' ' * 30
return progress_bar
def StateAsSimpleHTML(self):
@@ -333,7 +338,18 @@ def Display(self):
else:
assert 'Unexpected mode:', self.display_mode
- def UpdateDisplay(self):
+ def UpdateDisplay(self, final=False):
+ # This is now it's done, right?
+ now = (datetime.datetime.now() -
+ datetime.datetime(1, 12, 25)).total_seconds()
+ # Trying to have the state on if the process fails at early step.
+ self.display_update_period = min(0.5, self.display_update_period * 1.2)
+ if (now - self.recent_display_update_seconds <
+ self.display_update_period and
+ not final):
+ # Avoid frequent display updates slowing down execution.
+ return
+ self.recent_display_update_seconds = now
if self.display_mode == 'colab':
update_display(self.AsGraphViz(), display_id=self.display_id)
elif self.display_mode == 'terminal':
diff --git a/common/logica_test.py b/common/logica_test.py
index f80e4e2..3599ddc 100644
--- a/common/logica_test.py
+++ b/common/logica_test.py
@@ -55,12 +55,13 @@ def SetRunOnlyTests(cls, value):
@classmethod
def RunTest(cls, name, src, predicate, golden, user_flags,
- import_root=None, use_concertina=False):
+ import_root=None, use_concertina=False,
+ duckify_psql=False):
if cls.RUN_ONLY and name not in cls.RUN_ONLY:
return
RunTest(name, src, predicate, golden, user_flags,
cls.GOLDEN_RUN, cls.ANNOUNCE_TESTS,
- import_root, use_concertina)
+ import_root, use_concertina, duckify_psql)
@classmethod
def RunTypesTest(cls, name, src=None, golden=None):
@@ -107,19 +108,24 @@ def RunTypesTest(name, src=None, golden=None,
def RunTest(name, src, predicate, golden,
user_flags=None,
overwrite=False, announce=False,
- import_root=None, use_concertina=False):
+ import_root=None, use_concertina=False,
+ duckify_psql=False):
"""Run one test."""
if announce:
print('Running test:', name)
test_result = '{warning}RUNNING{end}'
print(color.Format('% 50s %s' % (name, test_result)))
-
+ if duckify_psql:
+ duck_src = '/tmp/%s.l' % name
+ with open(duck_src, 'w') as duck_source:
+ duck_source.write(open(src).read().replace('"psql"', '"duckdb"'))
+ src = duck_src
if use_concertina:
result = run_in_terminal.Run(src, predicate, display_mode='silent')
else:
result = logica_lib.RunPredicate(src, predicate,
- user_flags=user_flags,
- import_root=import_root)
+ user_flags=user_flags,
+ import_root=import_root)
# Hacky way to remove query that BQ prints.
if '+---' in result[200:]:
result = result[result.index('+---'):]
@@ -135,6 +141,10 @@ def RunTest(name, src, predicate, golden,
if result == golden_result:
test_result = '{ok}PASSED{end}'
else:
+ # print('\n' * 3)
+ # print(golden_result)
+ # print(result)
+ # print('\n' * 3)
p = subprocess.Popen(['diff', '--strip-trailing-cr', '-', golden], stdin=subprocess.PIPE)
p.communicate(result.encode())
if golden_result == 'This file does not exist. (<_<)':
diff --git a/compiler/dialect_libraries/duckdb_library.py b/compiler/dialect_libraries/duckdb_library.py
index 9b9e1ad..ff5165f 100644
--- a/compiler/dialect_libraries/duckdb_library.py
+++ b/compiler/dialect_libraries/duckdb_library.py
@@ -38,8 +38,9 @@
"(array_agg({arg_1} order by {value_1}))[1:{lim}]",
{arg_1: a.arg, value_1: a.value, lim: l});
-Array(arr) =
- SqlExpr("ArgMin({v}, {a})", {a:, v:}) :- Arrow(a, v) == arr;
+Array(a) = SqlExpr(
+ "ARRAY_AGG({value} order by {arg})",
+ {arg: a.arg, value: a.value});
RecordAsJson(r) = SqlExpr(
"ROW_TO_JSON({r})", {r:});
@@ -53,4 +54,11 @@
Num(a) = a;
Str(a) = a;
+NaturalHash(x) = ToInt64(SqlExpr("hash(cast({x} as string)) // cast(2 as ubigint)", {x:}));
+
+# This is unsafe to use because due to the way Logica compiles this number
+# will be unique for each use of the variable, which can be a pain to debug.
+# It is OK to use it as long as you undertand and are OK with the difficulty.
+UnsafeToUseUniqueNumber() = SqlExpr("nextval('eternal_logical_sequence')", {});
+
"""
diff --git a/compiler/dialects.py b/compiler/dialects.py
index 3e16598..38ed33e 100755
--- a/compiler/dialects.py
+++ b/compiler/dialects.py
@@ -399,12 +399,8 @@ def BuiltInFunctions(self):
return {
'Set': 'DistinctListAgg({0})',
'Element': "array_extract({0}, {1}+1)",
- 'Range': ('(select [n] from (with recursive t as'
- '(select 0 as n union all '
- 'select n + 1 as n from t where n + 1 < {0}) '
- 'select n from t) where n < {0})'),
+ 'Range': 'Range({0})',
'ValueOfUnnested': '{0}.unnested_pod',
- 'List': '[{0}]',
'Size': 'JSON_ARRAY_LENGTH({0})',
'Join': 'JOIN_STRINGS({0}, {1})',
'Count': 'COUNT(DISTINCT {0})',
@@ -412,8 +408,8 @@ def BuiltInFunctions(self):
'Sort': 'SortList({0})',
'MagicalEntangle': '(CASE WHEN {1} = 0 THEN {0} ELSE NULL END)',
'Format': 'Printf(%s)',
- 'Least': 'MIN(%s)',
- 'Greatest': 'MAX(%s)',
+ 'Least': 'LEAST(%s)',
+ 'Greatest': 'GREATEST(%s)',
'ToString': 'CAST(%s AS TEXT)',
'DateAddDay': "DATE({0}, {1} || ' days')",
'DateDiffDay': "CAST(JULIANDAY({0}) - JULIANDAY({1}) AS INT64)"
diff --git a/compiler/rule_translate.py b/compiler/rule_translate.py
index e516ca1..5f65415 100755
--- a/compiler/rule_translate.py
+++ b/compiler/rule_translate.py
@@ -81,10 +81,11 @@ def HeadToSelect(head):
return (select, aggregated_vars)
-def AllMentionedVariables(x, dive_in_combines=False):
+def AllMentionedVariables(x, dive_in_combines=False, this_is_select=False):
"""Extracting all variables mentioned in an expression."""
r = []
- if isinstance(x, dict) and 'variable' in x:
+ # In select there can be a variable named variable.
+ if isinstance(x, dict) and 'variable' in x and not this_is_select:
r.append(x['variable']['var_name'])
if isinstance(x, list):
for v in x:
@@ -249,7 +250,7 @@ def InternalVariables(self):
def AllVariables(self):
r = set()
- r |= AllMentionedVariables(self.select)
+ r |= AllMentionedVariables(self.select, this_is_select=True)
r |= AllMentionedVariables(self.vars_unification)
r |= AllMentionedVariables(self.constraints)
r |= AllMentionedVariables(self.unnestings)
diff --git a/compiler/universe.py b/compiler/universe.py
index 14d11f8..9f3713a 100755
--- a/compiler/universe.py
+++ b/compiler/universe.py
@@ -163,6 +163,11 @@ def Preamble(self):
'-- Initializing PostgreSQL environment.\n'
'set client_min_messages to warning;\n'
'create schema if not exists logica_home;\n\n')
+ elif self.Engine() == 'duckdb':
+ preamble += (
+ '-- Initializing DuckDB environment.\n'
+ 'create schema if not exists logica_home;\n'
+ 'create sequence if not exists eternal_logical_sequence;\n\n')
return preamble
def BuildFlagValues(self):
@@ -273,7 +278,7 @@ def OrderBy(self, predicate_name):
def Dataset(self):
default_dataset = 'logica_test'
# This change is intended for all engines in the future.
- if self.Engine() == 'psql':
+ if self.Engine() in ['psql', 'duckdb']:
default_dataset = 'logica_home'
if self.Engine() == 'sqlite' and 'logica_home' in self.AttachedDatabases():
default_dataset = 'logica_home'
@@ -296,7 +301,7 @@ def ShouldTypecheck(self):
engine_annotation = list(self.annotations['@Engine'].values())[0]
if 'type_checking' not in engine_annotation:
- if engine == 'psql':
+ if engine in ['psql', 'duckdb']:
return True
else:
return False
@@ -590,7 +595,13 @@ def CheckDistinctConsistency(self):
def UnfoldRecursion(self, rules):
annotations = Annotations(rules, {})
f = functors.Functors(rules)
- return f.UnfoldRecursions(annotations.annotations.get('@Recursive', {}))
+ depth_map = annotations.annotations.get('@Recursive', {})
+ # Annotations are not ready at this point.
+ # if (self.execution.annotations.Engine() == 'duckdb'):
+ # for p in depth_map:
+ # # DuckDB struggles with long querries.
+ # depth_map[p]['iterative'] = True
+ return f.UnfoldRecursions(depth_map)
def BuildUdfs(self):
"""Build UDF definitions."""
diff --git a/integration_tests/duckdb_combine_test.txt b/integration_tests/duckdb_combine_test.txt
new file mode 100644
index 0000000..c7fdb93
--- /dev/null
+++ b/integration_tests/duckdb_combine_test.txt
@@ -0,0 +1,12 @@
++-------+------+--------------+
+| col0 | col1 | col2 |
++-------+------+--------------+
+| test1 | 1 | [1] |
+| test1 | 2 | [2] |
+| test1 | 3 | [3] |
+| test1 | 4 | [4] |
+| test2 | 1 | [1, 2, 3, 4] |
+| test2 | 2 | [1, 3, 4] |
+| test2 | 3 | [1, 2, 4] |
+| test2 | 4 | [1, 2, 3, 4] |
++-------+------+--------------+
\ No newline at end of file
diff --git a/integration_tests/duckdb_flow_test.txt b/integration_tests/duckdb_flow_test.txt
new file mode 100644
index 0000000..12553c7
--- /dev/null
+++ b/integration_tests/duckdb_flow_test.txt
@@ -0,0 +1,18 @@
++------+------+--------------------+
+| col0 | col1 | logica_value |
++------+------+--------------------+
+| 0.0 | 1.0 | 2.9970000000000003 |
+| 0.0 | 4.0 | 9.994999999999997 |
+| 1.0 | 0.0 | 0.0 |
+| 1.0 | 2.0 | 0.0 |
+| 1.0 | 5.0 | 9.992999999999999 |
+| 2.0 | 1.0 | 6.995999999999998 |
+| 2.0 | 3.0 | 2.999 |
+| 2.0 | 4.0 | 0.0 |
+| 3.0 | 2.0 | 0.0 |
+| 3.0 | 5.0 | 0.0 |
+| 4.0 | 0.0 | 0.0 |
+| 4.0 | 2.0 | 9.994999999999997 |
+| 5.0 | 1.0 | 0.0 |
+| 5.0 | 3.0 | 9.992999999999999 |
++------+------+--------------------+
\ No newline at end of file
diff --git a/integration_tests/duckdb_graph_coloring_test.txt b/integration_tests/duckdb_graph_coloring_test.txt
new file mode 100644
index 0000000..43c0a03
--- /dev/null
+++ b/integration_tests/duckdb_graph_coloring_test.txt
@@ -0,0 +1,6 @@
++------+---------------+
+| col0 | col1 |
++------+---------------+
+| G1 | colorable |
+| G2 | not colorable |
++------+---------------+
\ No newline at end of file
diff --git a/integration_tests/duckdb_pair_test.txt b/integration_tests/duckdb_pair_test.txt
new file mode 100644
index 0000000..c5c671f
--- /dev/null
+++ b/integration_tests/duckdb_pair_test.txt
@@ -0,0 +1,5 @@
++------------------------------------------------------------------------------------------------------------------------------+
+| logica_value |
++------------------------------------------------------------------------------------------------------------------------------+
+| [{'word': 'sun', 'length': 3}, {'word': 'fire', 'length': 4}, {'word': 'wind', 'length': 4}, {'word': 'water', 'length': 5}] |
++------------------------------------------------------------------------------------------------------------------------------+
\ No newline at end of file
diff --git a/integration_tests/duckdb_purchase_test.txt b/integration_tests/duckdb_purchase_test.txt
new file mode 100644
index 0000000..4e0d6f7
--- /dev/null
+++ b/integration_tests/duckdb_purchase_test.txt
@@ -0,0 +1,12 @@
++-------------+---------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------+----------+
+| purchase_id | items | expensive_items | buyer_id |
++-------------+---------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------+----------+
+| 1 | [{'item': 'Soap', 'quantity': 3, 'price': 20}] | [{'item': 'Soap', 'more_expensive_than': ['Bread', 'Coffee', 'Firewood', 'Milk']}] | 11 |
+| 2 | [{'item': 'Milk', 'quantity': 1, 'price': 10}] | [{'item': 'Milk', 'more_expensive_than': ['Bread', 'Coffee']}] | 12 |
+| 3 | [{'item': 'Bread', 'quantity': 2, 'price': 5}, {'item': 'Coffee', 'quantity': 1, 'price': 7}] | [{'item': 'Coffee', 'more_expensive_than': ['Bread']}] | 13 |
+| 4 | [{'item': 'Firewood', 'quantity': 5, 'price': 15}, {'item': 'Soap', 'quantity': 1, 'price': 20}] | [{'item': 'Firewood', 'more_expensive_than': ['Bread', 'Coffee', 'Milk']}, {'item': 'Soap', 'more_expensive_than': ['Bread', 'Coffee', 'Firewood', 'Milk']}] | 14 |
+| 5 | [{'item': 'Bread', 'quantity': 1, 'price': 5}, {'item': 'Coffee', 'quantity': 2, 'price': 7}, {'item': 'Milk', 'quantity': 4, 'price': 10}] | [{'item': 'Coffee', 'more_expensive_than': ['Bread']}, {'item': 'Milk', 'more_expensive_than': ['Bread', 'Coffee']}] | 12 |
+| 6 | [{'item': 'Firewood', 'quantity': 1, 'price': 15}, {'item': 'Soap', 'quantity': 3, 'price': 20}] | [{'item': 'Firewood', 'more_expensive_than': ['Bread', 'Coffee', 'Milk']}, {'item': 'Soap', 'more_expensive_than': ['Bread', 'Coffee', 'Firewood', 'Milk']}] | 13 |
+| 7 | [{'item': 'Bread', 'quantity': 2, 'price': 5}, {'item': 'Coffee', 'quantity': 1, 'price': 7}, {'item': 'Milk', 'quantity': 1, 'price': 10}] | [{'item': 'Coffee', 'more_expensive_than': ['Bread']}, {'item': 'Milk', 'more_expensive_than': ['Bread', 'Coffee']}] | 14 |
+| 8 | [{'item': 'Firewood', 'quantity': 5, 'price': 15}, {'item': 'Soap', 'quantity': 1, 'price': 20}] | [{'item': 'Firewood', 'more_expensive_than': ['Bread', 'Coffee', 'Milk']}, {'item': 'Soap', 'more_expensive_than': ['Bread', 'Coffee', 'Firewood', 'Milk']}] | 11 |
++-------------+---------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------+----------+
\ No newline at end of file
diff --git a/integration_tests/duckdb_recursion_test.txt b/integration_tests/duckdb_recursion_test.txt
new file mode 100644
index 0000000..d91fe34
--- /dev/null
+++ b/integration_tests/duckdb_recursion_test.txt
@@ -0,0 +1,15 @@
++--------+-----------+--------------------------------------------------------------------------------------------+
+| vertex | component | distances |
++--------+-----------+--------------------------------------------------------------------------------------------+
+| 1 | 1 | [{'y': 5, 'd': 4}, {'y': 4, 'd': 3}, {'y': 3, 'd': 2}, {'y': 2, 'd': 1}, {'y': 1, 'd': 0}] |
+| 2 | 1 | [{'y': 5, 'd': 3}, {'y': 4, 'd': 2}, {'y': 3, 'd': 1}, {'y': 2, 'd': 0}, {'y': 1, 'd': 1}] |
+| 3 | 1 | [{'y': 5, 'd': 2}, {'y': 4, 'd': 1}, {'y': 3, 'd': 0}, {'y': 2, 'd': 1}, {'y': 1, 'd': 2}] |
+| 4 | 1 | [{'y': 5, 'd': 1}, {'y': 4, 'd': 0}, {'y': 3, 'd': 1}, {'y': 2, 'd': 2}, {'y': 1, 'd': 3}] |
+| 5 | 1 | [{'y': 5, 'd': 0}, {'y': 4, 'd': 1}, {'y': 3, 'd': 2}, {'y': 2, 'd': 3}, {'y': 1, 'd': 4}] |
+| 6 | 6 | [{'y': 8, 'd': 2}, {'y': 7, 'd': 1}, {'y': 6, 'd': 0}] |
+| 7 | 6 | [{'y': 8, 'd': 1}, {'y': 7, 'd': 0}, {'y': 6, 'd': 1}] |
+| 8 | 6 | [{'y': 8, 'd': 0}, {'y': 7, 'd': 1}, {'y': 6, 'd': 2}] |
+| 9 | 9 | [{'y': 11, 'd': 1}, {'y': 10, 'd': 1}, {'y': 9, 'd': 0}] |
+| 10 | 9 | [{'y': 11, 'd': 1}, {'y': 10, 'd': 0}, {'y': 9, 'd': 1}] |
+| 11 | 9 | [{'y': 11, 'd': 0}, {'y': 10, 'd': 1}, {'y': 9, 'd': 1}] |
++--------+-----------+--------------------------------------------------------------------------------------------+
\ No newline at end of file
diff --git a/integration_tests/psql_combine_test.l b/integration_tests/psql_combine_test.l
index cb3e241..8a53411 100644
--- a/integration_tests/psql_combine_test.l
+++ b/integration_tests/psql_combine_test.l
@@ -24,8 +24,9 @@ T(4);
@With(R);
R(x, l) :- T(x), l == (if x == 2 || x == 3 then [x] else []);
-P1(x, y) :- T(x), y List= x;
-P2(x, col1? List= y) distinct :- T(x), y in [1,2,3,4], R(x, l), ~(y in l);
+P1(x, y) :- T(x), y Array= x -> x;
+P2(x, col1? Array= y -> y) distinct :- T(x), y in [1,2,3,4], R(x, l), ~(y in l);
+@OrderBy(Test, "col0", "col1");
Test("test1", x, y) :- P1(x, y);
Test("test2", x, y) :- P2(x, y);
\ No newline at end of file
diff --git a/integration_tests/psql_graph_coloring_test.l b/integration_tests/psql_graph_coloring_test.l
index 0b0a90e..b921dea 100644
--- a/integration_tests/psql_graph_coloring_test.l
+++ b/integration_tests/psql_graph_coloring_test.l
@@ -22,9 +22,11 @@
-+-(left:x, right:y) = ToString(x) ++ ":" ++ ToString(y);
G("a" -+- i, "a" -+- (i + 1)) :- i in Range(6);
+@Recursive(L, 8, iterative: true);
L(1, 1, 2, 1) distinct;
L(x, y, x * 2, y) distinct :- L(a, b, x, y);
L(x, y, x, y * 3) distinct :- L(a, b, x, y);
+
G("b" -+- x -+- y, "b" -+- x1 -+- y1) :- L(x, y, x1, y1), x < 6, y < 6;
E(a, b) :- G(a, b) | G(b, a);
@@ -34,6 +36,7 @@ E(a, b) :- G(a, b) | G(b, a);
# Finding connected components.
#
+@Recursive(ComponentOf, 25);
ComponentOf(x) Min= x :- E(x);
ComponentOf(x) Min= ComponentOf(y) :- E(x, y);
diff --git a/integration_tests/psql_purchase_test.l b/integration_tests/psql_purchase_test.l
index f0585e4..6372fa7 100644
--- a/integration_tests/psql_purchase_test.l
+++ b/integration_tests/psql_purchase_test.l
@@ -21,7 +21,7 @@ Items(item: "Bread", price: 5);
Items(item: "Coffee", price: 7);
Items(item: "Firewood", price: 15);
-MoreExpensiveThan(item1) List= item2 :-
+MoreExpensiveThan(item1) Array= item2 -> item2 :-
Items(item: item1, price: price1),
Items(item: item2, price: price2),
price1 > price2;
@@ -52,16 +52,17 @@ Buyer(buyer_id: 13, purchase_id: 6);
Buyer(buyer_id: 14, purchase_id: 7);
Buyer(buyer_id: 11, purchase_id: 8);
-@OrderBy(Purchase, "purchase_id");
+@OrderBy(Purchase, "purchase_id", "items", "expensive_items", "buyer_id");
Purchase(purchase_id:, items:, expensive_items:, buyer_id:) :-
Buyer(buyer_id:, purchase_id:),
- items List= (
- {item:, quantity:, price:} :-
+ items Array= (
+ item -> {item:, quantity:, price:} :-
BuyEvent(purchase_id:, item:, quantity:),
Items(item:, price:)
),
- expensive_items List= (
- {item:, more_expensive_than: MoreExpensiveThan(item)} :-
+ expensive_items Array= (
+ {item:, more_expensive_than:} -> {item:, more_expensive_than:} :-
+ more_expensive_than = MoreExpensiveThan(item),
item_record in items,
item = item_record.item
);
diff --git a/integration_tests/psql_purchase_test.txt b/integration_tests/psql_purchase_test.txt
index f256571..a6ea504 100644
--- a/integration_tests/psql_purchase_test.txt
+++ b/integration_tests/psql_purchase_test.txt
@@ -1,12 +1,12 @@
+-------------+----------------------------------------------+----------------------------------------------------------------------------------+----------+
| purchase_id | items | expensive_items | buyer_id |
+-------------+----------------------------------------------+----------------------------------------------------------------------------------+----------+
-| 1 | {"(Soap,20,3)"} | {"(Soap,\"{Milk,Bread,Coffee,Firewood}\")"} | 11 |
+| 1 | {"(Soap,20,3)"} | {"(Soap,\"{Bread,Coffee,Firewood,Milk}\")"} | 11 |
| 2 | {"(Milk,10,1)"} | {"(Milk,\"{Bread,Coffee}\")"} | 12 |
| 3 | {"(Bread,5,2)","(Coffee,7,1)"} | {"(Coffee,{Bread})"} | 13 |
-| 4 | {"(Soap,20,1)","(Firewood,15,5)"} | {"(Soap,\"{Milk,Bread,Coffee,Firewood}\")","(Firewood,\"{Milk,Bread,Coffee}\")"} | 14 |
-| 5 | {"(Milk,10,4)","(Bread,5,1)","(Coffee,7,2)"} | {"(Milk,\"{Bread,Coffee}\")","(Coffee,{Bread})"} | 12 |
-| 6 | {"(Soap,20,3)","(Firewood,15,1)"} | {"(Soap,\"{Milk,Bread,Coffee,Firewood}\")","(Firewood,\"{Milk,Bread,Coffee}\")"} | 13 |
-| 7 | {"(Milk,10,1)","(Bread,5,2)","(Coffee,7,1)"} | {"(Milk,\"{Bread,Coffee}\")","(Coffee,{Bread})"} | 14 |
-| 8 | {"(Soap,20,1)","(Firewood,15,5)"} | {"(Soap,\"{Milk,Bread,Coffee,Firewood}\")","(Firewood,\"{Milk,Bread,Coffee}\")"} | 11 |
+| 4 | {"(Firewood,15,5)","(Soap,20,1)"} | {"(Firewood,\"{Bread,Coffee,Milk}\")","(Soap,\"{Bread,Coffee,Firewood,Milk}\")"} | 14 |
+| 5 | {"(Bread,5,1)","(Coffee,7,2)","(Milk,10,4)"} | {"(Coffee,{Bread})","(Milk,\"{Bread,Coffee}\")"} | 12 |
+| 6 | {"(Firewood,15,1)","(Soap,20,3)"} | {"(Firewood,\"{Bread,Coffee,Milk}\")","(Soap,\"{Bread,Coffee,Firewood,Milk}\")"} | 13 |
+| 7 | {"(Bread,5,2)","(Coffee,7,1)","(Milk,10,1)"} | {"(Coffee,{Bread})","(Milk,\"{Bread,Coffee}\")"} | 14 |
+| 8 | {"(Firewood,15,5)","(Soap,20,1)"} | {"(Firewood,\"{Bread,Coffee,Milk}\")","(Soap,\"{Bread,Coffee,Firewood,Milk}\")"} | 11 |
+-------------+----------------------------------------------+----------------------------------------------------------------------------------+----------+
\ No newline at end of file
diff --git a/integration_tests/psql_recursion_test.l b/integration_tests/psql_recursion_test.l
index e5c365f..06c4439 100644
--- a/integration_tests/psql_recursion_test.l
+++ b/integration_tests/psql_recursion_test.l
@@ -15,7 +15,7 @@ Edge(10, 11);
Edge(9, 11);
@OrderBy(Distance, "col0", "col1");
-@Recursive(Distance, 5);
+@Recursive(Distance, 50);
Distance(a, b) Min= 1 :- Edge(a, b);
Distance(a, a) Min= 0 :- Distance(a);
diff --git a/integration_tests/run_tests.py b/integration_tests/run_tests.py
index 9322baf..226d29b 100755
--- a/integration_tests/run_tests.py
+++ b/integration_tests/run_tests.py
@@ -20,7 +20,8 @@
def RunTest(name, src=None, golden=None, predicate=None,
- user_flags=None, import_root=None, use_concertina=False):
+ user_flags=None, import_root=None,
+ use_concertina=False, duckify_psql=False):
"""Run one test from this folder with TestManager."""
src = src or (name + ".l")
golden = golden or (name + ".txt")
@@ -32,7 +33,8 @@ def RunTest(name, src=None, golden=None, predicate=None,
predicate=predicate,
user_flags=user_flags,
import_root=import_root,
- use_concertina=use_concertina)
+ use_concertina=use_concertina,
+ duckify_psql=duckify_psql)
def RunAll(test_presto=False, test_trino=False):
@@ -97,6 +99,30 @@ def RunAll(test_presto=False, test_trino=False):
RunTest("sqlite_reachability")
RunTest("sqlite_element_test")
+ RunTest("duckdb_purchase_test",
+ src="psql_purchase_test.l",
+ duckify_psql=True, use_concertina=True)
+ RunTest("duckdb_pair_test",
+ src="psql_pair_test.l",
+ duckify_psql=True, use_concertina=True)
+ RunTest("duckdb_combine_test",
+ src="psql_combine_test.l",
+ duckify_psql=True, use_concertina=True)
+ RunTest("duckdb_recursion_test",
+ src="psql_recursion_test.l",
+ duckify_psql=True, use_concertina=True)
+ RunTest("duckdb_flow_test",
+ src="psql_flow_test.l",
+ duckify_psql=True, use_concertina=True)
+ RunTest("duckdb_graph_coloring_test",
+ src="psql_graph_coloring_test.l",
+ duckify_psql=True, use_concertina=True)
+
+ # There are not UDFs in DuckDB.
+ # RunTest("duckdb_udf_test",
+ # src="psql_udf_test.l",
+ # duckify_psql=True, use_concertina=True)
+
RunTest("psql_udf_test")
RunTest("psql_flow_test")
RunTest("psql_graph_coloring_test")
diff --git a/logica.py b/logica.py
index 1fdd42e..deea994 100755
--- a/logica.py
+++ b/logica.py
@@ -259,6 +259,12 @@ def main(argv):
[preamble] + defines_and_exports + [main_predicate_sql])
o = sqlite3_logica.RunSqlScript(statements_to_execute,
format).encode()
+ elif engine == 'duckdb':
+ import duckdb
+ df = duckdb.sql(formatted_sql).df()
+ o = sqlite3_logica.ArtisticTable(list(df.columns),
+ df.values
+ .tolist()).encode()
elif engine == 'psql':
connection_str = os.environ.get('LOGICA_PSQL_CONNECTION')
if connection_str:
diff --git a/tools/run_in_terminal.py b/tools/run_in_terminal.py
index 4ec63bf..e11b70b 100644
--- a/tools/run_in_terminal.py
+++ b/tools/run_in_terminal.py
@@ -42,7 +42,7 @@
class SqlRunner(object):
def __init__(self, engine):
self.engine = engine
- assert engine in ['sqlite', 'bigquery', 'psql']
+ assert engine in ['sqlite', 'bigquery', 'psql', 'duckdb']
if engine == 'sqlite':
self.connection = sqlite3_logica.SqliteConnect()
else:
@@ -58,7 +58,8 @@ def __init__(self, engine):
credentials, project = None, None
if engine == 'psql':
self.connection = psql_logica.ConnectToPostgres('environment')
-
+ if engine == 'duckdb':
+ self.connection = None # No connection needed!
self.bq_credentials = credentials
self.bq_project = project
@@ -101,6 +102,14 @@ def RunSQL(sql, engine, connection=None, is_final=False,
print(sql)
print("Error while executing SQL:\n%s" % e)
raise e
+ elif engine == 'duckdb':
+ import duckdb
+ if is_final:
+ df = duckdb.sql(sql).df()
+ return list(df.columns), df.values.tolist()
+ else:
+ duckdb.sql(sql)
+
else:
raise Exception('Logica only supports BigQuery, PostgreSQL and SQLite '
'for now.')