Skip to content

Commit 295ff4f

Browse files
committed
Clickhouse support.
1 parent 0deef11 commit 295ff4f

3 files changed

Lines changed: 146 additions & 2 deletions

File tree

common/clickhouse_logica.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
settings or via environment variables.
2222
2323
Environment variables:
24-
LOGICA_CLICKHOUSE_HOST (default: 127.0.0.1)
24+
LOGICA_CLICKHOUSE_HOST (default: 127.0.0.1; may include http:// or https://)
2525
LOGICA_CLICKHOUSE_PORT (default: 8123)
2626
LOGICA_CLICKHOUSE_USER (default: default)
2727
LOGICA_CLICKHOUSE_PASSWORD (default: "")
@@ -186,7 +186,18 @@ def HttpRequest(sql, *, settings):
186186
if v is None:
187187
continue
188188
params[str(k)] = str(v)
189-
url = f"http://{settings['host']}:{settings['port']}/?" + urllib.parse.urlencode(params)
189+
host = str(settings['host'])
190+
port = int(settings['port'])
191+
scheme = 'http'
192+
if '://' in host:
193+
parsed = urllib.parse.urlparse(host)
194+
scheme = parsed.scheme or scheme
195+
host = parsed.hostname or host
196+
port = int(parsed.port or (443 if scheme == 'https' else port))
197+
198+
base_url = f'{scheme}://{host}:{port}'
199+
200+
url = base_url.rstrip('/') + '/?' + urllib.parse.urlencode(params)
190201
req = urllib.request.Request(
191202
url,
192203
data=(sql + "\n").encode('utf-8'),
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
#!/usr/bin/env logica
2+
#
3+
# Copyright 2026 Logica Authors
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
17+
@Engine("sqlite");
18+
19+
# Explainable paths demo: "why does A depend on B?"
20+
#
21+
# We compute a shortest witness path as an array of nodes.
22+
23+
Depends("frontend", "api");
24+
Depends("api", "billing");
25+
Depends("billing", "payments_db");
26+
Depends("api", "events");
27+
Depends("events", "analytics");
28+
29+
# Shortest path search (same pattern as integration_tests/sqlite_reachability.l).
30+
Path(source:, target:, length? Min= 1, path? ArgMin= ([source] -> 1)) distinct :-
31+
Depends(source, target);
32+
33+
Path(source: a, target: c,
34+
length? Min= l1 + l2,
35+
path? ArgMin= ArrayConcat(p1, p2) -> (l1 + l2)) distinct :-
36+
Path(source: a, target: b, length: l1, path: p1),
37+
Path(source: b, target: c, length: l2, path: p2);
38+
39+
Explain(source:, target:, witness_path:) :-
40+
Path(source:, target:, path: p),
41+
witness_path == ArrayConcat(p, [target]),
42+
source != target;
43+
44+
# One concrete "why" question.
45+
WhyFrontendToDb(witness_path) :-
46+
Explain(source: "frontend", target: "payments_db", witness_path: witness_path);
47+
48+
Q(source:, target:, witness_path:) :-
49+
Explain(source:, target:, witness_path:);
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
#!/usr/bin/env logica
2+
#
3+
# Copyright 2026 Logica Authors
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
17+
@Engine("sqlite");
18+
19+
# A tiny demo of "team contracts": promises vs requirements.
20+
# The output is a list of objectively checkable violations.
21+
22+
Team("billing");
23+
Team("risk");
24+
Team("analytics");
25+
26+
System("charge_api");
27+
System("events_stream");
28+
29+
Owns("billing", "charge_api");
30+
Owns("analytics", "events_stream");
31+
32+
# Promises: what the owner team claims to provide.
33+
# Example: latency_p95_ms <= 200
34+
PromiseSla("billing", "charge_api", "latency_p95_ms", 200);
35+
PromiseSla("analytics", "events_stream", "delivery_lag_p95_s", 60);
36+
37+
# Requirements: what a consumer team needs.
38+
NeedSla("risk", "charge_api", "latency_p95_ms", 150);
39+
NeedSla("billing", "events_stream", "delivery_lag_p95_s", 10);
40+
41+
# Schema promises/requirements (simplified).
42+
PromiseFieldType("billing", "charge_api", "amount", "Int64");
43+
NeedFieldType("risk", "charge_api", "amount", "Float64");
44+
45+
# Violations.
46+
SlaViolation(consumer, owner, system, metric, need, promise) :-
47+
NeedSla(consumer, system, metric, need),
48+
Owns(owner, system),
49+
PromiseSla(owner, system, metric, promise),
50+
need < promise;
51+
52+
MissingPromise(consumer, owner, system, metric, need) :-
53+
NeedSla(consumer, system, metric, need),
54+
Owns(owner, system),
55+
~PromiseSla(owner, system, metric, _);
56+
57+
TypeViolation(consumer, owner, system, field, need_type, promise_type) :-
58+
NeedFieldType(consumer, system, field, need_type),
59+
Owns(owner, system),
60+
PromiseFieldType(owner, system, field, promise_type),
61+
need_type != promise_type;
62+
63+
Q(kind, consumer, owner, system, subject, expected, actual) :-
64+
SlaViolation(consumer, owner, system, metric, need, promise),
65+
kind == "sla",
66+
subject == metric,
67+
expected == ToString(need),
68+
actual == ToString(promise);
69+
70+
Q(kind, consumer, owner, system, subject, expected, actual) :-
71+
MissingPromise(consumer, owner, system, metric, need),
72+
kind == "missing_promise",
73+
subject == metric,
74+
expected == ToString(need),
75+
actual == "(no promise)";
76+
77+
Q(kind, consumer, owner, system, subject, expected, actual) :-
78+
TypeViolation(consumer, owner, system, field, need_type, promise_type),
79+
kind == "type",
80+
subject == field,
81+
expected == need_type,
82+
actual == promise_type;
83+
84+
@OrderBy(Q, "col0");

0 commit comments

Comments
 (0)