first commit

This commit is contained in:
2026-01-24 11:30:02 +08:00
commit d99832f66b
26 changed files with 3456 additions and 0 deletions

226
tests/test_algebra.py Normal file
View File

@@ -0,0 +1,226 @@
"""
Tests for Phase 2: Algebra System
"""
from pysecondo.core.nested_list import atom
from pysecondo.core.types import BaseType
from pysecondo.algebras.standard import StandardAlgebra
from pysecondo.algebras.base import AlgebraManager
import sys
sys.path.insert(0, '.')
def test_algebra_registration():
"""Test algebra registration"""
print("Testing Algebra Registration...")
manager = AlgebraManager()
std_algebra = StandardAlgebra()
manager.register_algebra("StandardAlgebra", std_algebra)
# Check algebra is registered
assert "StandardAlgebra" in manager.list_algebras()
# Check operators are indexed
ops = manager.list_operators()
assert "+" in ops
assert "-" in ops
assert "*" in ops
assert "/" in ops
assert "and" in ops
assert "or" in ops
assert "not" in ops
print(" ✓ Algebra registration tests passed")
def test_arithmetic_operators():
"""Test arithmetic operators"""
print("Testing Arithmetic Operators...")
manager = AlgebraManager()
manager.register_algebra("StandardAlgebra", StandardAlgebra())
# Test addition
add_op = manager.get_operator("+")
# Type map: int + int = int
result_type = add_op.type_map([BaseType.INT, BaseType.INT])
assert result_type == BaseType.INT
# Type map: int + real = real
result_type = add_op.type_map([BaseType.INT, BaseType.REAL])
assert result_type == BaseType.REAL
# Value map: 5 + 3 = 8
result = add_op.value_map([atom(5), atom(3)])
assert result.value == 8
# Test subtraction
sub_op = manager.get_operator("-")
result = sub_op.value_map([atom(10), atom(3)])
assert result.value == 7
# Test multiplication
mul_op = manager.get_operator("*")
result = mul_op.value_map([atom(6), atom(7)])
assert result.value == 42
# Test division
div_op = manager.get_operator("/")
result = div_op.value_map([atom(10), atom(2)])
assert result.value == 5.0
print(" ✓ Arithmetic operators tests passed")
def test_comparison_operators():
"""Test comparison operators"""
print("Testing Comparison Operators...")
manager = AlgebraManager()
manager.register_algebra("StandardAlgebra", StandardAlgebra())
# Test less than
lt_op = manager.get_operator("<")
result_type = lt_op.type_map([BaseType.INT, BaseType.INT])
assert result_type == BaseType.BOOL
result = lt_op.value_map([atom(3), atom(5)])
assert result.value is True
result = lt_op.value_map([atom(5), atom(3)])
assert result.value is False
# Test greater than
gt_op = manager.get_operator(">")
result = gt_op.value_map([atom(10), atom(5)])
assert result.value is True
# Test equal
eq_op = manager.get_operator("=")
result = eq_op.value_map([atom(5), atom(5)])
assert result.value is True
result = eq_op.value_map([atom(5), atom(3)])
assert result.value is False
# Test not equal
ne_op = manager.get_operator("!=")
result = ne_op.value_map([atom(5), atom(3)])
assert result.value is True
print(" ✓ Comparison operators tests passed")
def test_logical_operators():
"""Test logical operators"""
print("Testing Logical Operators...")
manager = AlgebraManager()
manager.register_algebra("StandardAlgebra", StandardAlgebra())
# Test and
and_op = manager.get_operator("and")
result = and_op.value_map([atom(True), atom(True)])
assert result.value is True
result = and_op.value_map([atom(True), atom(False)])
assert result.value is False
# Test or
or_op = manager.get_operator("or")
result = or_op.value_map([atom(True), atom(False)])
assert result.value is True
result = or_op.value_map([atom(False), atom(False)])
assert result.value is False
# Test not
not_op = manager.get_operator("not")
result = not_op.value_map([atom(True)])
assert result.value is False
result = not_op.value_map([atom(False)])
assert result.value is True
print(" ✓ Logical operators tests passed")
def test_type_checking():
"""Test type checking in operators"""
print("Testing Type Checking...")
manager = AlgebraManager()
manager.register_algebra("StandardAlgebra", StandardAlgebra())
add_op = manager.get_operator("+")
# Valid types
try:
add_op.type_map([BaseType.INT, BaseType.INT])
except TypeError:
assert False, "Should not raise TypeError for valid types"
# Invalid types
try:
add_op.type_map([BaseType.STRING, BaseType.INT])
assert False, "Should raise TypeError for invalid types"
except TypeError as e:
assert "requires" in str(e).lower()
# Wrong number of arguments
try:
add_op.type_map([BaseType.INT])
assert False, "Should raise TypeError for wrong argument count"
except TypeError as e:
assert "expects 2" in str(e)
print(" ✓ Type checking tests passed")
def test_complex_expressions():
"""Test complex expressions using multiple operators"""
print("Testing Complex Expressions...")
manager = AlgebraManager()
manager.register_algebra("StandardAlgebra", StandardAlgebra())
# Expression: (5 + 3) * 2 = 16
add_op = manager.get_operator("+")
mul_op = manager.get_operator("*")
temp = add_op.value_map([atom(5), atom(3)])
result = mul_op.value_map([temp, atom(2)])
assert result.value == 16
# Expression: (10 > 5) and (3 < 7) = True
gt_op = manager.get_operator(">")
lt_op = manager.get_operator("<")
and_op = manager.get_operator("and")
temp1 = gt_op.value_map([atom(10), atom(5)])
temp2 = lt_op.value_map([atom(3), atom(7)])
result = and_op.value_map([temp1, temp2])
assert result.value is True
print(" ✓ Complex expressions tests passed")
if __name__ == "__main__":
print("=" * 50)
print("Phase 2: Algebra System Tests")
print("=" * 50)
test_algebra_registration()
test_arithmetic_operators()
test_comparison_operators()
test_logical_operators()
test_type_checking()
test_complex_expressions()
print("\n" + "=" * 50)
print("All Phase 2 tests passed! ✓")
print("=" * 50)

179
tests/test_core.py Normal file
View File

@@ -0,0 +1,179 @@
"""
Tests for Phase 1: Core functionality
"""
from pysecondo.storage.memory import MemoryStorage
from pysecondo.core.type_system import TypeChecker
from pysecondo.core.types import (
BaseType, TupleType, RelationType, Attribute,
parse_type, type_to_string
)
from pysecondo.core.nested_list import NestedList, atom, list_nl
import sys
sys.path.insert(0, '.')
def test_nested_list():
"""Test nested list creation and operations"""
print("Testing NestedList...")
# Atomic values
nl_int = atom(42)
nl_str = atom("Beijing")
nl_bool = atom(True)
assert nl_int.is_atom()
assert nl_int.value == 42
assert str(nl_int) == "42"
assert nl_str.is_atom()
assert nl_str.value == "Beijing"
assert str(nl_str) == '"Beijing"'
# Lists
nl_list = list_nl(1, 2, 3)
assert nl_list.is_list()
assert len(nl_list) == 3
assert str(nl_list) == "(1 2 3)"
# Nested structures (tuple)
nl_tuple = list_nl("Beijing", 21540000)
assert str(nl_tuple) == '("Beijing" 21540000)'
# Nested structures (relation)
nl_rel = list_nl(
list_nl("Beijing", 21540000),
list_nl("Shanghai", 24280000)
)
assert str(nl_rel) == '(("Beijing" 21540000) ("Shanghai" 24280000))'
# to_python conversion
assert nl_int.to_python() == 42
assert nl_list.to_python() == [1, 2, 3]
assert nl_tuple.to_python() == ["Beijing", 21540000]
print(" ✓ NestedList tests passed")
def test_type_system():
"""Test type system"""
print("Testing Type System...")
# Parse basic types
int_type = parse_type("int")
assert int_type == BaseType.INT
string_type = parse_type("string")
assert string_type == BaseType.STRING
# Parse tuple type
tuple_type_str = "(tuple ((Name string)(Population int)))"
tuple_type = parse_type(tuple_type_str)
assert isinstance(tuple_type, TupleType)
assert len(tuple_type.attributes) == 2
assert tuple_type.attributes[0].name == "Name"
assert tuple_type.attributes[0].type == BaseType.STRING
# Parse relation type
rel_type_str = "(rel (tuple ((Name string)(Population int))))"
rel_type = parse_type(rel_type_str)
assert isinstance(rel_type, RelationType)
assert isinstance(rel_type.tuple_type, TupleType)
# Type to string
assert type_to_string(BaseType.INT) == "int"
assert "Name" in type_to_string(tuple_type)
assert "(rel" in type_to_string(rel_type)
print(" ✓ Type system tests passed")
def test_type_checker():
"""Test type checking"""
print("Testing Type Checker...")
checker = TypeChecker()
# Check basic types
assert checker.check(atom(42), BaseType.INT)
assert checker.check(atom("hello"), BaseType.STRING)
assert not checker.check(atom("hello"), BaseType.INT)
# Check tuple type
city_tuple_type = TupleType([
Attribute("Name", BaseType.STRING),
Attribute("Population", BaseType.INT)
])
beijing = list_nl("Beijing", 21540000)
assert checker.check(beijing, city_tuple_type)
wrong_tuple = list_nl(123, 21540000)
assert not checker.check(wrong_tuple, city_tuple_type)
# Check relation type
cities_rel_type = RelationType(city_tuple_type)
cities = list_nl(
list_nl("Beijing", 21540000),
list_nl("Shanghai", 24280000)
)
assert checker.check(cities, cities_rel_type)
print(" ✓ Type checker tests passed")
def test_storage():
"""Test in-memory storage"""
print("Testing Memory Storage...")
storage = MemoryStorage()
# Create object
city_tuple_type = TupleType([
Attribute("Name", BaseType.STRING),
Attribute("Population", BaseType.INT)
])
beijing = list_nl("Beijing", 21540000)
storage.create_object("beijing", beijing, city_tuple_type)
# Get object
retrieved = storage.get_object("beijing")
assert retrieved == beijing
retrieved_type = storage.get_type("beijing")
assert retrieved_type == city_tuple_type
# Update object
shanghai = list_nl("Shanghai", 24280000)
storage.update_object("beijing", shanghai, city_tuple_type)
assert storage.get_object("beijing") == shanghai
# List objects
storage.create_object("city2", beijing, city_tuple_type)
objects = storage.list_objects()
assert "beijing" in objects
assert "city2" in objects
assert len(objects) == 2
# Delete object
storage.delete_object("city2")
assert len(storage.list_objects()) == 1
print(" ✓ Storage tests passed")
if __name__ == "__main__":
print("=" * 50)
print("Phase 1: Core Functionality Tests")
print("=" * 50)
test_nested_list()
test_type_system()
test_type_checker()
test_storage()
print("\n" + "=" * 50)
print("All Phase 1 tests passed! ✓")
print("=" * 50)

311
tests/test_relation.py Normal file
View File

@@ -0,0 +1,311 @@
"""
Tests for Phase 3: Relation Algebra
"""
from pysecondo.core.nested_list import atom, list_nl
from pysecondo.core.types import BaseType, TupleType, RelationType, Attribute
from pysecondo.query_processor import QueryProcessor
from pysecondo.storage.memory import MemoryStorage
from pysecondo.algebras.relation import RelationAlgebra
from pysecondo.algebras.standard import StandardAlgebra
from pysecondo.algebras.base import AlgebraManager
import sys
sys.path.insert(0, '.')
def test_create_relation():
"""Test creating relations"""
print("Testing Create Relation...")
storage = MemoryStorage()
algebra_manager = AlgebraManager()
algebra_manager.register_algebra("StandardAlgebra", StandardAlgebra())
algebra_manager.register_algebra(
"RelationAlgebra",
RelationAlgebra(storage)
)
qp = QueryProcessor(algebra_manager, storage)
# Create a relation
qp.execute_create(
"cities",
"(rel (tuple ((Name string)(Population int))))"
)
# Check it exists
assert storage.object_exists("cities")
obj_type = storage.get_type("cities")
assert isinstance(obj_type, RelationType)
print(" ✓ Create relation tests passed")
def test_update_relation():
"""Test updating relations with data"""
print("Testing Update Relation...")
storage = MemoryStorage()
algebra_manager = AlgebraManager()
algebra_manager.register_algebra("StandardAlgebra", StandardAlgebra())
algebra_manager.register_algebra(
"RelationAlgebra",
RelationAlgebra(storage)
)
qp = QueryProcessor(algebra_manager, storage)
# Create and populate a relation
qp.execute_create(
"cities",
"(rel (tuple ((Name string)(Population int))))"
)
# Insert data
cities_data = list_nl(
list_nl("Beijing", 21540000),
list_nl("Shanghai", 24280000),
list_nl("Guangzhou", 14040000),
)
qp.execute_update("cities", cities_data)
# Verify data
cities = qp.lookup_identifier("cities")
assert len(cities) == 3
print(" ✓ Update relation tests passed")
def test_feed_consume():
"""Test feed and consume operators"""
print("Testing Feed and Consume...")
storage = MemoryStorage()
algebra_manager = AlgebraManager()
algebra_manager.register_algebra("StandardAlgebra", StandardAlgebra())
algebra_manager.register_algebra(
"RelationAlgebra",
RelationAlgebra(storage)
)
qp = QueryProcessor(algebra_manager, storage)
# Create and populate a relation
qp.execute_create(
"cities",
"(rel (tuple ((Name string)(Population int))))"
)
cities_data = list_nl(
list_nl("Beijing", 21540000),
list_nl("Shanghai", 24280000),
list_nl("Guangzhou", 14040000),
)
qp.execute_update("cities", cities_data)
# Test feed: relation -> stream
cities = qp.lookup_identifier("cities")
cities_type = qp.get_identifier_type("cities")
feed_op = algebra_manager.get_operator("feed")
stream = feed_op.value_map([cities])
assert len(stream) == 3
# Test consume: stream -> relation
consume_op = algebra_manager.get_operator("consume")
result = consume_op.value_map([stream])
assert len(result) == 3
print(" ✓ Feed and consume tests passed")
def test_count():
"""Test count operator"""
print("Testing Count Operator...")
storage = MemoryStorage()
algebra_manager = AlgebraManager()
algebra_manager.register_algebra("StandardAlgebra", StandardAlgebra())
algebra_manager.register_algebra(
"RelationAlgebra",
RelationAlgebra(storage)
)
qp = QueryProcessor(algebra_manager, storage)
# Create and populate a relation
qp.execute_create(
"cities",
"(rel (tuple ((Name string)(Population int))))"
)
cities_data = list_nl(
list_nl("Beijing", 21540000),
list_nl("Shanghai", 24280000),
list_nl("Guangzhou", 14040000),
list_nl("Shenzhen", 17560000),
)
qp.execute_update("cities", cities_data)
# Test count
cities = qp.lookup_identifier("cities")
feed_op = algebra_manager.get_operator("feed")
count_op = algebra_manager.get_operator("count")
stream = feed_op.value_map([cities])
count_result = count_op.value_map([stream])
assert count_result.value == 4
print(" ✓ Count operator tests passed")
def test_filter():
"""Test filter operator"""
print("Testing Filter Operator...")
storage = MemoryStorage()
algebra_manager = AlgebraManager()
algebra_manager.register_algebra("StandardAlgebra", StandardAlgebra())
algebra_manager.register_algebra(
"RelationAlgebra",
RelationAlgebra(storage)
)
qp = QueryProcessor(algebra_manager, storage)
# Create and populate a relation
qp.execute_create(
"cities",
"(rel (tuple ((Name string)(Population int))))"
)
cities_data = list_nl(
list_nl("Beijing", 21540000),
list_nl("Shanghai", 24280000),
list_nl("Guangzhou", 14040000),
list_nl("Shenzhen", 17560000),
)
qp.execute_update("cities", cities_data)
# Test filter with true (pass all)
cities = qp.lookup_identifier("cities")
feed_op = algebra_manager.get_operator("feed")
filter_op = algebra_manager.get_operator("filter")
stream = feed_op.value_map([cities])
filtered = filter_op.value_map([stream, atom(True)])
assert len(filtered) == 4
# Test filter with false (pass none)
filtered = filter_op.value_map([stream, atom(False)])
assert len(filtered) == 0
print(" ✓ Filter operator tests passed")
def test_query_pipeline():
"""Test a complete query pipeline"""
print("Testing Query Pipeline...")
storage = MemoryStorage()
algebra_manager = AlgebraManager()
algebra_manager.register_algebra("StandardAlgebra", StandardAlgebra())
algebra_manager.register_algebra(
"RelationAlgebra",
RelationAlgebra(storage)
)
qp = QueryProcessor(algebra_manager, storage)
# Setup: create cities relation
qp.execute_create(
"cities",
"(rel (tuple ((Name string)(Population int))))"
)
cities_data = list_nl(
list_nl("Beijing", 21540000),
list_nl("Shanghai", 24280000),
list_nl("Guangzhou", 14040000),
list_nl("Shenzhen", 17560000),
list_nl("Hangzhou", 12200000),
)
qp.execute_update("cities", cities_data)
# Query: cities feed count
# Equivalent to: SELECT COUNT(*) FROM cities
cities = qp.lookup_identifier("cities")
feed_op = algebra_manager.get_operator("feed")
count_op = algebra_manager.get_operator("count")
stream = feed_op.value_map([cities])
count = count_op.value_map([stream])
assert count.value == 5
# Query: cities feed filter[true] consume
# Equivalent to: SELECT * FROM cities
filter_op = algebra_manager.get_operator("filter")
consume_op = algebra_manager.get_operator("consume")
stream = feed_op.value_map([cities])
filtered = filter_op.value_map([stream, atom(True)])
result = consume_op.value_map([filtered])
assert len(result) == 5
print(" ✓ Query pipeline tests passed")
def test_type_checking():
"""Test type checking for relation operators"""
print("Testing Type Checking for Relations...")
algebra_manager = AlgebraManager()
algebra_manager.register_algebra("StandardAlgebra", StandardAlgebra())
storage = MemoryStorage()
algebra_manager.register_algebra(
"RelationAlgebra",
RelationAlgebra(storage)
)
# Test feed type mapping
feed_op = algebra_manager.get_operator("feed")
tuple_type = TupleType([
Attribute("Name", BaseType.STRING),
Attribute("Population", BaseType.INT)
])
rel_type = RelationType(tuple_type)
result_type = feed_op.type_map([rel_type])
assert result_type == tuple_type
# Test consume type mapping
consume_op = algebra_manager.get_operator("consume")
result_type = consume_op.type_map([tuple_type])
assert isinstance(result_type, RelationType)
assert result_type.tuple_type == tuple_type
# Test count type mapping
count_op = algebra_manager.get_operator("count")
result_type = count_op.type_map([tuple_type])
assert result_type == BaseType.INT
print(" ✓ Type checking tests passed")
if __name__ == "__main__":
print("=" * 50)
print("Phase 3: Relation Algebra Tests")
print("=" * 50)
test_create_relation()
test_update_relation()
test_feed_consume()
test_count()
test_filter()
test_query_pipeline()
test_type_checking()
print("\n" + "=" * 50)
print("All Phase 3 tests passed! ✓")
print("=" * 50)

220
tests/test_repl.py Normal file
View File

@@ -0,0 +1,220 @@
"""
Tests for Phase 4: Query Processing & REPL
"""
from pysecondo.core.nested_list import atom, list_nl
from pysecondo.core.types import BaseType, TupleType, RelationType
from pysecondo.storage.memory import MemoryStorage
from pysecondo.algebras.relation import RelationAlgebra
from pysecondo.algebras.standard import StandardAlgebra
from pysecondo.algebras.base import AlgebraManager
from pysecondo.parser.evaluator import Evaluator
from pysecondo.parser.parser import Parser, parse_query, CreateCommand, UpdateCommand, QueryCommand
import sys
sys.path.insert(0, '.')
def test_parser_create():
"""Test parsing CREATE commands"""
print("Testing Parser (CREATE)...")
parser = Parser()
# Test create command
cmd = parser.parse(
'create cities : (rel (tuple ((Name string)(Population int))))')
assert isinstance(cmd, CreateCommand)
assert cmd.name == "cities"
assert "(rel" in cmd.type_str
print(" ✓ CREATE parsing tests passed")
def test_parser_update():
"""Test parsing UPDATE commands"""
print("Testing Parser (UPDATE)...")
parser = Parser()
# Test update command
cmd = parser.parse(
'update cities := (("Beijing" 21540000)("Shanghai" 24280000))')
assert isinstance(cmd, UpdateCommand)
assert cmd.name == "cities"
assert "Beijing" in cmd.value
print(" ✓ UPDATE parsing tests passed")
def test_parser_query():
"""Test parsing QUERY commands"""
print("Testing Parser (QUERY)...")
parser = Parser()
# Test query command
cmd = parser.parse('query cities feed count')
assert isinstance(cmd, QueryCommand)
assert cmd.expression == "cities feed count"
# Test arithmetic query
cmd = parser.parse('query 5 + 3')
assert isinstance(cmd, QueryCommand)
assert cmd.expression == "5 + 3"
print(" ✓ QUERY parsing tests passed")
def test_parser_expressions():
"""Test expression tokenization"""
print("Testing Expression Tokenization...")
parser = Parser()
# Simple identifier
tokens = parser.parse_expression("cities")
assert tokens == ["cities"]
# Operator chain
tokens = parser.parse_expression("cities feed consume")
assert tokens == ["cities", "feed", "consume"]
# Arithmetic
tokens = parser.parse_expression("5 + 3")
assert tokens == ["5", "+", "3"]
# Complex expression
tokens = parser.parse_expression("cities feed filter true consume")
assert tokens == ["cities", "feed", "filter", "true", "consume"]
print(" ✓ Expression tokenization tests passed")
def test_evaluator_arithmetic():
"""Test evaluating arithmetic expressions"""
print("Testing Evaluator (Arithmetic)...")
storage = MemoryStorage()
algebra_manager = AlgebraManager()
algebra_manager.register_algebra("StandardAlgebra", StandardAlgebra())
evaluator = Evaluator(algebra_manager, storage)
# Test: 5 + 3
tokens = ["5", "+", "3"]
value, value_type = evaluator.evaluate(tokens)
assert value.value == 8
# Test: 10 - 4
tokens = ["10", "-", "4"]
value, value_type = evaluator.evaluate(tokens)
assert value.value == 6
# Test: 6 * 7
tokens = ["6", "*", "7"]
value, value_type = evaluator.evaluate(tokens)
assert value.value == 42
print(" ✓ Arithmetic evaluation tests passed")
def test_evaluator_identifiers():
"""Test evaluating identifiers"""
print("Testing Evaluator (Identifiers)...")
storage = MemoryStorage()
algebra_manager = AlgebraManager()
algebra_manager.register_algebra("StandardAlgebra", StandardAlgebra())
evaluator = Evaluator(algebra_manager, storage)
# Store a value
storage.create_object("x", atom(42), BaseType.INT)
# Test: x
tokens = ["x"]
value, value_type = evaluator.evaluate(tokens)
assert value.value == 42
print(" ✓ Identifier evaluation tests passed")
def test_evaluator_relations():
"""Test evaluating relation expressions"""
print("Testing Evaluator (Relations)...")
storage = MemoryStorage()
algebra_manager = AlgebraManager()
algebra_manager.register_algebra("StandardAlgebra", StandardAlgebra())
algebra_manager.register_algebra(
"RelationAlgebra", RelationAlgebra(storage))
evaluator = Evaluator(algebra_manager, storage)
# Create a relation
cities_type = RelationType(TupleType([]))
storage.create_object("cities", list_nl(), cities_type)
# Test: cities feed
tokens = ["cities", "feed"]
value, value_type = evaluator.evaluate(tokens)
print(" ✓ Relation evaluation tests passed")
def test_end_to_end():
"""Test end-to-end query execution"""
print("Testing End-to-End Queries...")
storage = MemoryStorage()
algebra_manager = AlgebraManager()
algebra_manager.register_algebra("StandardAlgebra", StandardAlgebra())
algebra_manager.register_algebra(
"RelationAlgebra", RelationAlgebra(storage))
parser = Parser()
evaluator = Evaluator(algebra_manager, storage)
# Create relation
create_cmd = parser.parse(
'create cities : (rel (tuple ((Name string)(Population int))))')
assert isinstance(create_cmd, CreateCommand)
# Insert data (using storage directly for simplicity)
from pysecondo.core.types import parse_type
cities_type = parse_type('(rel (tuple ((Name string)(Population int))))')
cities_data = list_nl(
list_nl("Beijing", 21540000),
list_nl("Shanghai", 24280000),
list_nl("Guangzhou", 14040000),
)
storage.create_object("cities", cities_data, cities_type)
# Query: cities feed count
query_cmd = parser.parse('query cities feed count')
assert isinstance(query_cmd, QueryCommand)
tokens = parser.parse_expression(query_cmd.expression)
value, value_type = evaluator.evaluate(tokens)
assert value.value == 3
print(" ✓ End-to-end query tests passed")
if __name__ == "__main__":
print("=" * 50)
print("Phase 4: Query Processing & REPL Tests")
print("=" * 50)
test_parser_create()
test_parser_update()
test_parser_query()
test_parser_expressions()
test_evaluator_arithmetic()
test_evaluator_identifiers()
test_evaluator_relations()
test_end_to_end()
print("\n" + "=" * 50)
print("All Phase 4 tests passed! ✓")
print("=" * 50)