validity parsing

main
Ziyang Hu 2 years ago
parent 71f525e80c
commit f1ceb27ac6

@ -1,7 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="CPP_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/cozopy/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/cozorocks/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/cozoserver/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>

@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
</profile>
</component>

@ -11,15 +11,26 @@
<configuration PROFILE_NAME="Debug" ENABLED="true" CONFIG_NAME="Debug" />
</configurations>
</component>
<component name="CargoProjects">
<cargoProject FILE="$PROJECT_DIR$/Cargo.toml" />
</component>
<component name="ChangeListManager">
<list default="true" id="fb7002fa-47b1-45d9-bc6d-711b16e752b3" name="Changes" comment="">
<change beforePath="$PROJECT_DIR$/.idea/.gitignore" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/.name" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/cozo.iml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/misc.xml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/modules.xml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/rocksdb.iml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/vcs.xml" beforeDir="false" />
<list default="true" id="fb7002fa-47b1-45d9-bc6d-711b16e752b3" name="Changes" comment="regenerate idea files">
<change beforePath="$PROJECT_DIR$/.idea/cozo.iml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/cozo.iml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Cargo.toml" beforeDir="false" afterPath="$PROJECT_DIR$/Cargo.toml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/cozohttp/Cargo.toml" beforeDir="false" afterPath="$PROJECT_DIR$/cozoserver/Cargo.toml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/cozohttp/build.rs" beforeDir="false" afterPath="$PROJECT_DIR$/cozoserver/build.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/cozohttp/src/main.rs" beforeDir="false" afterPath="$PROJECT_DIR$/cozoserver/src/main.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/cozopy/.gitignore" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/cozopy/Cargo.toml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/cozopy/air-routes-latest-edges.csv" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/cozopy/air-routes-latest-nodes.csv" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/cozopy/air_routes.py" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/cozopy/cozo.py" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/cozopy/smoke.py" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/cozopy/src/lib.rs" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/src/data/id.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/data/id.rs" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
@ -32,6 +43,9 @@
<component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component>
<component name="MacroExpansionManager">
<option name="directoryName" value="g3ljnk28" />
</component>
<component name="MarkdownSettingsMigration">
<option name="stateVersion" value="1" />
</component>
@ -43,14 +57,62 @@
</component>
<component name="PropertiesComponent"><![CDATA[{
"keyToString": {
"ASKED_SHARE_PROJECT_CONFIGURATION_FILES": "true",
"RunOnceActivity.OpenProjectViewOnStart": "true",
"RunOnceActivity.ShowReadmeOnStart": "true",
"RunOnceActivity.cidr.known.project.marker": "true",
"WebServerToolWindowFactoryState": "false",
"cf.first.check.clang-format": "false",
"cidr.known.project.marker": "true"
"cidr.known.project.marker": "true",
"node.js.detected.package.eslint": "true",
"node.js.selected.package.eslint": "(autodetect)",
"nodejs_package_manager_path": "yarn",
"org.rust.cargo.project.model.PROJECT_DISCOVERY": "true"
}
}]]></component>
<component name="RunManager">
<configuration name="Test data::id::p" type="CargoCommandRunConfiguration" factoryName="Cargo Command" temporary="true">
<option name="command" value="test --package cozo --lib data::id::p -- --exact" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<option name="channel" value="DEFAULT" />
<option name="requiredFeatures" value="true" />
<option name="allFeatures" value="false" />
<option name="emulateTerminal" value="false" />
<option name="withSudo" value="false" />
<option name="buildTarget" value="REMOTE" />
<option name="backtrace" value="SHORT" />
<envs />
<option name="isRedirectInput" value="false" />
<option name="redirectInputPath" value="" />
<method v="2">
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
</method>
</configuration>
<configuration default="true" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="command" value="run" />
<option name="channel" value="DEFAULT" />
<option name="requiredFeatures" value="true" />
<option name="allFeatures" value="false" />
<option name="emulateTerminal" value="false" />
<option name="withSudo" value="false" />
<option name="buildTarget" value="REMOTE" />
<option name="backtrace" value="SHORT" />
<envs />
<option name="isRedirectInput" value="false" />
<option name="redirectInputPath" value="" />
<method v="2">
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
</method>
</configuration>
<recent_temporary>
<list>
<item itemvalue="Cargo.Test data::id::p" />
</list>
</recent_temporary>
</component>
<component name="RustProjectSettings">
<option name="toolchainHomeDirectory" value="$PROJECT_DIR$/../.cargo/bin" />
</component>
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
@ -59,10 +121,21 @@
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1663161524540</updated>
<workItem from="1663161527741" duration="2000" />
<workItem from="1663161527741" duration="2380000" />
</task>
<task id="LOCAL-00001" summary="regenerate idea files">
<created>1663161616722</created>
<option name="number" value="00001" />
<option name="presentableId" value="LOCAL-00001" />
<option name="project" value="LOCAL" />
<updated>1663161616722</updated>
</task>
<option name="localTasksCounter" value="2" />
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
<option name="version" value="3" />
</component>
<component name="Vcs.Log.Tabs.Properties">
<option name="TAB_STATES">
<map>
@ -74,4 +147,8 @@
</map>
</option>
</component>
<component name="VcsManagerConfiguration">
<MESSAGE value="regenerate idea files" />
<option name="LAST_COMMIT_MESSAGE" value="regenerate idea files" />
</component>
</project>

@ -52,4 +52,4 @@ cozorocks = { path = "cozorocks" }
#debug = true
[workspace]
members = ["cozorocks", "cozohttp", "cozopy"]
members = ["cozorocks", "cozoserver"]

141
cozopy/.gitignore vendored

@ -1,141 +0,0 @@
### Python template
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
db_test_flights

@ -1,16 +0,0 @@
[package]
name = "cozopy"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
name = "cozopy"
crate-type = ["cdylib"]
[dependencies]
serde_json = "1.0.81"
anyhow = "1.0.58"
env_logger = "0.9.0"
pyo3 = { version = "0.16.5", features = ["extension-module"] }
cozo = { path = ".." }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -1,132 +0,0 @@
import json
import time
import pandas as pd
from cozo import *
def remove_nan(d):
return {k: v for (k, v) in d.items() if v is not None and v == v}
# put airport {
# iata: string,
# icao: string indexed,
# city: string identity,
# country: ref
# }
# put whatever: string;
# retract attr
def insert_data(destroy_on_exit):
db = CozoDb('db_test_flights', destroy_on_exit=destroy_on_exit)
try:
payload = [
*DefAttrs('country')
.code(Typing.string, index=Indexing.identity)
.desc(Typing.string)(),
*DefAttrs('continent')
.code(Typing.string, index=Indexing.identity)
.desc(Typing.string)(),
*DefAttrs('airport')
.iata(Typing.string, index=Indexing.identity)
.icao(Typing.string, index=Indexing.indexed)
.city(Typing.string, index=Indexing.indexed)
.desc(Typing.string)
.region(Typing.string, index=Indexing.indexed)
.country(Typing.ref)
.runways(Typing.int)
.longest(Typing.int)
.altitude(Typing.int)
.lat(Typing.float)
.lon(Typing.float)(),
*DefAttrs('route')
.src(Typing.ref)
.dst(Typing.ref)
.distance(Typing.int)(),
*DefAttrs('geo')
.contains(Typing.ref)(),
]
start_time = time.time()
tx_res = db.tx_attr(payload)['results']
end_time = time.time()
print(f'{len(tx_res)} attributes added in {(end_time - start_time) * 1000:.3f}ms')
insertions = []
nodes = pd.read_csv('air-routes-latest-nodes.csv', index_col=0)
continents = nodes[nodes['~label'] == 'continent']
for tuple in continents.itertuples():
put_payload = remove_nan(
{'_temp_id': str(tuple.Index), 'continent.code': tuple._3, 'continent.desc': tuple._5})
insertions.append(Put(put_payload))
country_idx = {}
countries = nodes[nodes['~label'] == 'country']
for tuple in countries.itertuples():
put_payload = remove_nan({'_temp_id': str(tuple.Index), 'country.code': tuple._3, 'country.desc': tuple._5})
country_idx[tuple._3] = str(tuple.Index)
insertions.append(Put(put_payload))
airports = nodes[nodes['~label'] == 'airport']
for tuple in airports.itertuples():
put_payload = remove_nan({
'_temp_id': str(tuple.Index),
'airport.iata': tuple._3,
'airport.icao': None if tuple._4 == 'none' else tuple._4,
'airport.desc': tuple._5,
'airport.region': tuple._6,
'airport.runways': int(tuple._7),
'airport.longest': int(tuple._8),
'airport.altitude': int(tuple._9),
'airport.country': country_idx[tuple._10],
'airport.city': tuple._11,
'airport.lat': tuple._12,
'airport.lon': tuple._13
})
insertions.append(Put(put_payload))
edges = pd.read_csv('air-routes-latest-edges.csv', index_col=0)
for tuple in edges[edges['~label'] == 'route'].itertuples():
payload = remove_nan(
{'route.src': str(tuple._1), 'route.dst': str(tuple._2), 'route.distance': int(tuple._4)})
insertions.append(Put(payload))
for tuple in edges[edges['~label'] == 'contains'].itertuples():
payload = remove_nan({'_temp_id': str(tuple._1), 'geo.contains': str(tuple._2)})
insertions.append(Put(payload))
start_time = time.time()
d_res = db.tx(insertions)['results']
end_time = time.time()
print(f'{len(d_res)} node data added in {(end_time - start_time) * 1000:.3f}ms')
print(f'{len(d_res) / (end_time - start_time):.0f} insertions per second')
with open('air-routes-data.json', 'w', encoding='utf-8') as f:
for row in insertions:
json.dump(row['put'], f, ensure_ascii=False)
f.write('\n')
except Exception as e:
print(f'data already exists? {e}')
return db
if __name__ == '__main__':
db = insert_data(False)
start_time = time.time()
# res = db.run([Q(['?c', '?code', '?desc'],
# Disj(T.country.code('?c', 'CU'),
# Unify('?c', 10000239)),
# T.country.code('?c', '?code'),
# T.country.desc('?c', '?desc'))])
res = db.run([Q([Max('?n')],
T.route.distance('?a', '?n'))])
end_time = time.time()
print(json.dumps(res, indent=2))
print(f'{len(res)} results fetched in {(end_time - start_time) * 1000:.3f}ms')

@ -1,250 +0,0 @@
import json
from enum import Enum
from cozopy import CozoDbPy
class CozoDb:
def __init__(self, *args, **kwargs):
self.inner = CozoDbPy(*args, **kwargs)
def tx_attr(self, payload):
return json.loads(self.inner.transact_attributes(json.dumps({'attrs': payload}, ensure_ascii=False)))
def tx(self, payload):
return json.loads(self.inner.transact_triples(json.dumps({'tx': payload}, ensure_ascii=False)))
def run(self, q, out=None, limit=None, offset=None, sort=None):
payload = {'q': q}
if out is not None:
payload['out'] = out
if limit is not None:
payload['limit'] = limit
if offset is not None:
payload['offset'] = offset
if sort is not None:
payload['sort'] = [convert_sort_arg(arg) for arg in sort]
return json.loads(self.inner.run_query(json.dumps(payload, ensure_ascii=False)))
def convert_sort_arg(arg):
if arg.startswith('+'):
return {arg[1:]: 'asc'}
elif arg.startswith('-'):
return {arg[1:]: 'desc'}
else:
return {arg: 'asc'}
class Typing(str, Enum):
ref = 'ref'
component = 'component'
bool = 'bool'
int = 'int'
float = 'float'
string = 'string'
name = 'name'
uuid = 'uuid'
timestamp = 'timestamp'
bytes = 'bytes'
list = 'list'
class Cardinality(str, Enum):
one = 'one'
many = 'many'
class Indexing(str, Enum):
none = 'none'
indexed = 'indexed'
unique = 'unique'
identity = 'identity'
def Attribute(name, typing, id, cardinality, index, history):
ret = {
'name': name,
'type': typing,
'cardinality': cardinality,
'index': index,
'history': history
}
if id is not None:
ret['id'] = id
return ret
def PutAttr(name, typing, id=None, cardinality=Cardinality.one, index=Indexing.none, history=False):
return {
'put': Attribute(name, typing, id, cardinality, index, history)
}
def RetractAttr(name, typing, id, cardinality, index, history):
return {
'retract': Attribute(name, typing, id, cardinality, index, history)
}
def Put(d):
return {'put': d}
def Retract(d):
return {'retract': d}
def Pull(variable, spec):
return {'pull': variable, 'spec': spec}
class DefAttrs:
def __init__(self, prefix):
self.prefix = prefix
self.attrs = []
def __call__(self, *args, **kwargs):
return self.attrs
def __getattr__(self, item):
return DefAttributesHelper(self, item)
class DefAttributesHelper:
def __init__(self, parent, name):
self.parent = parent
self.name = name
def __call__(self, typing, id=None, cardinality=Cardinality.one, index=Indexing.none, history=False):
name = f'{self.parent.prefix}.{self.name}'
self.parent.attrs.append({
'put': Attribute(name, typing, id, cardinality, index, history)
})
return self.parent
class TripleClass:
def __init__(self, attr_name):
self._attr_name = attr_name
def __getattr__(self, name):
if self._attr_name is None:
return self.__class__(name)
else:
return self.__class__(self._attr_name + '.' + name)
def __call__(self, entity, value):
if self._attr_name is None:
raise Exception("you need to set the triple attribute first")
return [entity, self._attr_name, value]
T = TripleClass(None)
class RuleClass:
def __init__(self, rule_name):
self._rule_name = rule_name
self._at = None
def __getattr__(self, name):
if self._rule_name is None:
return self.__class__(name)
elif name == 'at':
def closure(time):
self._at = time
return self
return closure
else:
raise Exception("cannot nest rule name")
def __call__(self, *args):
if self._rule_name is None:
raise Exception("you need to set the rule name first")
ret = {'rule': self._rule_name, 'args': list(args)}
if self._at:
ret['at'] = self._at
return ret
R = RuleClass(None)
Q = RuleClass('?')
class OpClass:
def __init__(self, op_name):
self._op_name = op_name
def __getattr__(self, name):
if self._op_name is None:
return self.__class__(name)
else:
raise Exception("cannot nest op name")
def __call__(self, *args):
if self._op_name is None:
raise Exception("you need to set the op name first")
ret = {'op': self._op_name, 'args': list(args)}
return ret
Gt = OpClass('Gt')
Lt = OpClass('Lt')
Ge = OpClass('Ge')
Le = OpClass('Le')
Eq = OpClass('Eq')
Neq = OpClass('Neq')
Add = OpClass('Add')
Sub = OpClass('Sub')
Mul = OpClass('Mul')
Div = OpClass('Div')
StrCat = OpClass('StrCat')
class AggrClass:
def __init__(self, aggr_name):
self._aggr_name = aggr_name
def __getattr__(self, name):
if self._aggr_name is None:
return self.__class__(name)
else:
raise Exception("cannot nest aggr name")
def __call__(self, symb):
if self._aggr_name is None:
raise Exception("you need to set the predicate name first")
ret = {'aggr': self._aggr_name, 'symb': symb}
return ret
Count = AggrClass('count')
Min = AggrClass('min')
Max = AggrClass('max')
def Const(item):
return {'const': item}
def Conj(*items):
return {'conj': items}
def Disj(*items):
return {'disj': items}
def NotExists(item):
return {'not_exists': item}
def Unify(binding, expr):
return {'unify': binding, 'expr': expr}
__all__ = ['Gt', 'Lt', 'Ge', 'Le', 'Eq', 'Neq', 'Add', 'Sub', 'Mul', 'Div', 'Q', 'T', 'R', 'Const', 'Conj', 'Disj',
'NotExists', 'CozoDb', 'Typing', 'Cardinality', 'Indexing', 'PutAttr', 'RetractAttr', 'Attribute', 'Put',
'Retract', 'Pull', 'StrCat', 'Unify', 'DefAttrs', 'Count', 'Min', 'Max']

@ -1,84 +0,0 @@
from cozo import *
if __name__ == '__main__':
db = CozoDb('_test', destroy_on_exit=True)
res = db.tx_attr(
DefAttrs('person')
.idd(Typing.string, index=Indexing.identity)
.first_name(Typing.string, index=Indexing.indexed)
.last_name(Typing.string, index=Indexing.indexed)
.age(Typing.int, index=Indexing.indexed)
.friend(Typing.ref, cardinality=Cardinality.many, index=Indexing.indexed)
.weight(Typing.float, index=Indexing.indexed)
.covid(Typing.bool)()
)
print(res)
print(db.tx_attr([
PutAttr('person.id', Typing.string, id=res['results'][0][0], cardinality=Cardinality.one,
index=Indexing.identity),
RetractAttr('person.covid', Typing.bool, id=res['results'][-1][0], cardinality=Cardinality.one, history=False,
index=Indexing.indexed),
]))
print(db.tx([
Put({'_temp_id': 'alice',
'person.first_name': 'Alice',
'person.age': 7,
'person.last_name': 'Amorist',
'person.id': 'alice_amorist',
'person.weight': 25,
'person.friend': 'eve'}),
Put({'_temp_id': 'bob',
'person.first_name': 'Bob',
'person.age': 70,
'person.last_name': 'Wonderland',
'person.id': 'bob_wonderland',
'person.weight': 100,
'person.friend': 'alice'
}),
Put({'_temp_id': 'eve',
'person.first_name': 'Eve',
'person.age': 18,
'person.last_name': 'Faking',
'person.id': 'eve_faking',
'person.weight': 50,
'person.friend': [
'alice',
'bob',
{'person.first_name': 'Charlie',
'person.age': 22,
'person.last_name': 'Goodman',
'person.id': 'charlie_goodman',
'person.weight': 120,
'person.friend': 'eve'}
]
}),
Put({'_temp_id': 'david',
'person.first_name': 'David',
'person.age': 7,
'person.last_name': 'Dull',
'person.id': 'david_dull',
'person.weight': 25,
'person.friend': {
'_temp_id': 'george',
'person.first_name': 'George',
'person.age': 7,
'person.last_name': 'Geomancer',
'person.id': 'george_geomancer',
'person.weight': 25,
'person.friend': 'george'}
}),
]))
res = db.run([
R.ff(['?a', '?b'],
T.person.friend('?a', '?b')),
R.ff(['?a', '?b'],
T.person.friend('?a', '?c'),
R.ff('?c', '?b')),
Q(['?a'],
T.person.first_name('?a', '?n'),
T.person.first_name('?alice', 'Alice'),
NotExists(R.ff('?a', '?alice'))
),
],
out={'friend': Pull('?a', ['person.first_name'])})
print(res)

@ -1,69 +0,0 @@
// #[cfg(not(target_env = "msvc"))]
// use tikv_jemallocator::Jemalloc;
//
// #[cfg(not(target_env = "msvc"))]
// #[global_allocator]
// static GLOBAL: Jemalloc = Jemalloc;
//
use pyo3::exceptions::PyException;
use pyo3::prelude::*;
use cozo::{Db, DbBuilder};
#[pyclass(extends = PyException)]
struct ErrorBridge(cozo::Error);
trait PyResultExt<T> {
fn into_py_res(self) -> PyResult<T>;
}
impl<T> PyResultExt<T> for anyhow::Result<T> {
fn into_py_res(self) -> PyResult<T> {
match self {
Ok(t) => Ok(t),
Err(e) => Err(PyException::new_err(e.to_string())),
}
}
}
#[pyclass]
struct CozoDbPy {
db: Db,
}
#[pymethods]
impl CozoDbPy {
#[new]
#[args(create_if_missing = true, destroy_on_exit = false)]
fn new(path: &str, create_if_missing: bool, destroy_on_exit: bool) -> PyResult<Self> {
let _ = env_logger::try_init();
let builder = DbBuilder::default()
.path(path)
.create_if_missing(create_if_missing)
.destroy_on_exit(destroy_on_exit);
let db = Db::build(builder).into_py_res()?;
Ok(Self { db })
}
pub fn transact_attributes(&self, py: Python<'_>, payload: &str) -> PyResult<String> {
let payload: serde_json::Value = serde_json::from_str(payload).unwrap();
let ret = py.allow_threads(|| self.db.transact_attributes(&payload).into_py_res())?;
Ok(ret.to_string())
}
pub fn transact_triples(&self, py: Python<'_>, payload: &str) -> PyResult<String> {
let payload: serde_json::Value = serde_json::from_str(payload).unwrap();
let ret = py.allow_threads(|| self.db.transact_triples(&payload).into_py_res())?;
Ok(ret.to_string())
}
pub fn run_query(&self, py: Python<'_>, payload: &str) -> PyResult<String> {
let payload: serde_json::Value = serde_json::from_str(payload).unwrap();
let ret = py.allow_threads(|| self.db.run_query(&payload).into_py_res())?;
Ok(ret.to_string())
}
}
#[pymodule]
fn cozopy(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
m.add_class::<CozoDbPy>()?;
m.add_class::<ErrorBridge>()?;
Ok(())
}

@ -1,5 +1,5 @@
[package]
name = "cozohttp"
name = "cozoserver"
version = "0.1.0"
edition = "2021"
license = "AGPL-3.0-or-later"

@ -1,8 +1,8 @@
use std::fmt::{Debug, Formatter};
use std::time::{SystemTime, UNIX_EPOCH};
use chrono::{DateTime, TimeZone, Utc};
use miette::Diagnostic;
use chrono::{DateTime, NaiveDate, TimeZone, Utc};
use miette::{Diagnostic, Result};
use serde_derive::{Deserialize, Serialize};
use thiserror::Error;
@ -45,7 +45,9 @@ impl From<i64> for Validity {
#[error("Cannot convert {0:?} to Validity")]
#[diagnostic(code(parser::bad_validity))]
#[diagnostic(help(
"Validity can be represented by integers, or strings according to RFC2822 or RFC3339"
"Validity can be represented by integers interpreted as microseconds since the UNIX epoch, \
or strings according to RFC3339, \
or in the date-only format 'YYYY-MM-DD' (with implicit time at midnight UTC)"
))]
struct BadValidityError(DataValue, #[label] SourceSpan);
@ -59,17 +61,26 @@ impl TryFrom<Expr> for Validity {
return Ok(v.into());
}
if let Some(s) = value.get_string() {
let dt = DateTime::parse_from_rfc2822(s)
.or_else(|_| DateTime::parse_from_rfc3339(s))
.map_err(|_| BadValidityError(value, span))?;
let sysdt: SystemTime = dt.into();
let timestamp = sysdt.duration_since(UNIX_EPOCH).unwrap().as_micros() as i64;
return Ok(Self(timestamp));
if let Ok(dt) = DateTime::parse_from_rfc2822(s) {
let sysdt: SystemTime = dt.into();
let timestamp = sysdt.duration_since(UNIX_EPOCH).unwrap().as_micros() as i64;
return Ok(Self(timestamp));
}
if let Ok(nd) = NaiveDate::parse_from_str(s, "%Y-%m-%d") {
return Ok(Self(nd.and_hms(0, 0, 0).timestamp() * 1_000_000));
}
}
Err(BadValidityError(value, span).into())
}
}
#[test]
fn p() {
let x = NaiveDate::parse_from_str("2015-09-05", "%Y-%m-%d").unwrap();
let x = x.and_hms(0, 0, 0).timestamp();
println!("{:?}", x)
}
impl Debug for Validity {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
if *self == Validity::MIN {

Loading…
Cancel
Save