Skip to content

Commit d8cab8a

Browse files
committed
Merge remote-tracking branch 'origin/main' into nn-meta-provenance
2 parents 9340453 + 29469c9 commit d8cab8a

98 files changed

Lines changed: 1026 additions & 835 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Makefile

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
# make force-printer-X Force-regenerate a single printer.
1515
# make test Run tests for all languages.
1616
# make test-X Run tests for one language (X = python, julia, go).
17+
# make update-bins Regenerate binary test files from .lqp sources.
1718
# make update-snapshots Regenerate Python snapshot test outputs.
1819
# make lint-python Run ruff lint and format checks.
1920
# make format-python Auto-format Python code with ruff.
@@ -79,7 +80,7 @@ JL_PROTO_GENERATED := \
7980
force-parsers force-parser-python force-parser-julia force-parser-go \
8081
printers printer-python printer-julia printer-go \
8182
force-printers force-printer-python force-printer-julia force-printer-go \
82-
test test-python update-snapshots test-julia test-go \
83+
test test-python update-bins update-snapshots test-julia test-go \
8384
test-meta check-python check-meta lint-meta format-meta \
8485
lint-python format-python clean
8586

@@ -192,6 +193,12 @@ test: test-meta test-python test-julia test-go
192193
test-python: $(PY_PARSER) $(PY_PROTO_GENERATED) check-python
193194
cd sdks/python && uv run python -m pytest
194195

196+
update-bins: $(PY_PARSER) $(PY_PROTO_GENERATED)
197+
@for lqp in tests/lqp/*.lqp; do \
198+
name=$$(basename "$$lqp" .lqp); \
199+
cd sdks/python && uv run lqp "../../$$lqp" --bin --out > "../../tests/bin/$${name}.bin" && cd ../..; \
200+
done
201+
195202
update-snapshots: $(PY_PARSER) $(PY_PROTO_GENERATED)
196203
cd sdks/python && uv run python -m pytest --snapshot-update
197204

meta/src/meta/templates/parser.go.template

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ package lqp
1010

1111
import (
1212
"crypto/sha256"
13+
"encoding/binary"
1314
"fmt"
1415
"math"
1516
"math/big"
@@ -432,16 +433,10 @@ func (p *Parser) startFragment(fragmentID *pb.FragmentId) *pb.FragmentId {{
432433
}}
433434

434435
func (p *Parser) relationIdFromString(name string) *pb.RelationId {{
435-
// Create RelationId from string hash (matching Python implementation)
436-
// Python uses: int(hashlib.sha256(name.encode()).hexdigest()[:16], 16)
437-
// This takes only first 8 bytes (16 hex chars) as id_low, id_high is always 0
438-
// Python interprets the hex as big-endian, so we read bytes in big-endian order
439436
hash := sha256.Sum256([]byte(name))
440-
var low uint64
441-
for i := 0; i < 8; i++ {{
442-
low = (low << 8) | uint64(hash[i])
443-
}}
444-
high := uint64(0)
437+
// Use big-endian and the lower 128 bits of the hash, consistent with pyrel.
438+
high := binary.BigEndian.Uint64(hash[16:24])
439+
low := binary.BigEndian.Uint64(hash[24:32])
445440
relationId := &pb.RelationId{{IdLow: low, IdHigh: high}}
446441

447442
// Store the mapping for the current fragment if we're inside one

meta/src/meta/templates/parser.jl.template

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -278,8 +278,9 @@ end
278278
function relation_id_from_string(parser::ParserState, name::String)
279279
# Create RelationId from string and track mapping for debug info
280280
hash_bytes = sha256(name)
281-
id_low = Base.parse(UInt64, bytes2hex(hash_bytes[1:8]), base=16)
282-
id_high = UInt64(0)
281+
# Use big-endian and the lower 128 bits of the hash, consistent with pyrel.
282+
id_high = ntoh(reinterpret(UInt64, hash_bytes[17:24])[1])
283+
id_low = ntoh(reinterpret(UInt64, hash_bytes[25:32])[1])
283284
relation_id = Proto.RelationId(id_low, id_high)
284285

285286
# Store the mapping for the current fragment if we're inside one

meta/src/meta/templates/parser.py.template

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -298,8 +298,10 @@ class Parser:
298298

299299
def relation_id_from_string(self, name: str) -> Any:
300300
"""Create RelationId from string and track mapping for debug info."""
301-
id_low = int(hashlib.sha256(name.encode()).hexdigest()[:16], 16)
302-
id_high = 0
301+
hash_bytes = hashlib.sha256(name.encode()).digest()
302+
# Use big-endian and the lower 128 bits of the hash, consistent with pyrel.
303+
id_high = int.from_bytes(hash_bytes[16:24], byteorder='big')
304+
id_low = int.from_bytes(hash_bytes[24:32], byteorder='big')
303305
relation_id = logic_pb2.RelationId(id_low=id_low, id_high=id_high)
304306

305307
# Store the mapping for the current fragment if we're inside one

sdks/go/src/hash_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package lqp
2+
3+
import "testing"
4+
5+
// TestRelationIdFromString verifies that relation_id_from_string produces
6+
// the same id across all SDKs (Julia, Python, Go).
7+
func TestRelationIdFromString(t *testing.T) {
8+
p := NewParser([]Token{})
9+
rid := p.relationIdFromString("my_relation")
10+
if rid.IdLow != 0xf2fc83ec57cf8fbc {
11+
t.Errorf("id_low: got 0x%016x, want 0xf2fc83ec57cf8fbc", rid.IdLow)
12+
}
13+
if rid.IdHigh != 0x503f7dc862f367b7 {
14+
t.Errorf("id_high: got 0x%016x, want 0x503f7dc862f367b7", rid.IdHigh)
15+
}
16+
}

sdks/go/src/parser.go

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ package lqp
1010

1111
import (
1212
"crypto/sha256"
13+
"encoding/binary"
1314
"fmt"
1415
"math"
1516
"math/big"
@@ -458,16 +459,10 @@ func (p *Parser) startFragment(fragmentID *pb.FragmentId) *pb.FragmentId {
458459
}
459460

460461
func (p *Parser) relationIdFromString(name string) *pb.RelationId {
461-
// Create RelationId from string hash (matching Python implementation)
462-
// Python uses: int(hashlib.sha256(name.encode()).hexdigest()[:16], 16)
463-
// This takes only first 8 bytes (16 hex chars) as id_low, id_high is always 0
464-
// Python interprets the hex as big-endian, so we read bytes in big-endian order
465462
hash := sha256.Sum256([]byte(name))
466-
var low uint64
467-
for i := 0; i < 8; i++ {
468-
low = (low << 8) | uint64(hash[i])
469-
}
470-
high := uint64(0)
463+
// Use big-endian and the lower 128 bits of the hash, consistent with pyrel.
464+
high := binary.BigEndian.Uint64(hash[16:24])
465+
low := binary.BigEndian.Uint64(hash[24:32])
471466
relationId := &pb.RelationId{IdLow: low, IdHigh: high}
472467

473468
// Store the mapping for the current fragment if we're inside one

sdks/julia/LogicalQueryProtocol.jl/src/parser.jl

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -304,8 +304,9 @@ end
304304
function relation_id_from_string(parser::ParserState, name::String)
305305
# Create RelationId from string and track mapping for debug info
306306
hash_bytes = sha256(name)
307-
id_low = Base.parse(UInt64, bytes2hex(hash_bytes[1:8]), base=16)
308-
id_high = UInt64(0)
307+
# Use big-endian and the lower 128 bits of the hash, consistent with pyrel.
308+
id_high = ntoh(reinterpret(UInt64, hash_bytes[17:24])[1])
309+
id_low = ntoh(reinterpret(UInt64, hash_bytes[25:32])[1])
309310
relation_id = Proto.RelationId(id_low, id_high)
310311

311312
# Store the mapping for the current fragment if we're inside one

sdks/julia/LogicalQueryProtocol.jl/test/parser_tests.jl

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,16 @@ end
112112
@test r.scale == 1
113113
end
114114

115+
@testitem "Parser - relation_id_from_string" setup=[ParserSetup] begin
116+
using LogicalQueryProtocol.Parser: ParserState, Token, relation_id_from_string
117+
118+
# All SDKs must produce the same id for the same string.
119+
parser = ParserState(Token[])
120+
rid = relation_id_from_string(parser, "my_relation")
121+
@test rid.id_low == 0xf2fc83ec57cf8fbc
122+
@test rid.id_high == 0x503f7dc862f367b7
123+
end
124+
115125
@testitem "parse_transaction entry point" setup=[ParserSetup] begin
116126
input = "(transaction (epoch (writes) (reads)))"
117127
result = Parser.parse_transaction(input)

sdks/python/src/lqp/gen/parser.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -326,8 +326,10 @@ def start_fragment(
326326

327327
def relation_id_from_string(self, name: str) -> Any:
328328
"""Create RelationId from string and track mapping for debug info."""
329-
id_low = int(hashlib.sha256(name.encode()).hexdigest()[:16], 16)
330-
id_high = 0
329+
hash_bytes = hashlib.sha256(name.encode()).digest()
330+
# Use big-endian and the lower 128 bits of the hash, consistent with pyrel.
331+
id_high = int.from_bytes(hash_bytes[16:24], byteorder='big')
332+
id_low = int.from_bytes(hash_bytes[24:32], byteorder='big')
331333
relation_id = logic_pb2.RelationId(id_low=id_low, id_high=id_high)
332334

333335
# Store the mapping for the current fragment if we're inside one

sdks/python/tests/test_parser.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,20 @@
33
import pytest
44
from pytest_snapshot.plugin import Snapshot
55

6-
from lqp.gen.parser import ParseError, parse, parse_fragment, parse_transaction
6+
from lqp.gen.parser import ParseError, Parser, parse, parse_fragment, parse_transaction
77
from lqp.proto.v1 import fragments_pb2, transactions_pb2
88

99
from .utils import BIN_SNAPSHOTS_DIR, get_lqp_input_files
1010

1111

12+
def test_relation_id_from_string():
13+
"""All SDKs must produce the same id for the same string."""
14+
parser = Parser([])
15+
rid = parser.relation_id_from_string("my_relation")
16+
assert rid.id_low == 0xF2FC83EC57CF8FBC
17+
assert rid.id_high == 0x503F7DC862F367B7
18+
19+
1220
@pytest.mark.parametrize("input_file", get_lqp_input_files())
1321
def test_parse_lqp(snapshot: Snapshot, input_file):
1422
"""Test that each input file can be parsed and matches its binary snapshot."""

0 commit comments

Comments
 (0)