arcadedb-embedded-native 26.10.2.dev1__py3-none-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. arcadedb_embedded_native-26.10.2.dev1.dist-info/METADATA +114 -0
  2. arcadedb_embedded_native-26.10.2.dev1.dist-info/RECORD +41 -0
  3. arcadedb_embedded_native-26.10.2.dev1.dist-info/WHEEL +5 -0
  4. arcadedb_embedded_native-26.10.2.dev1.dist-info/licenses/LICENSE +201 -0
  5. arcadedb_embedded_native-26.10.2.dev1.dist-info/licenses/NOTICE +25 -0
  6. arcadedb_embedded_native-26.10.2.dev1.dist-info/licenses/THIRD_PARTY_NOTICES.md +3629 -0
  7. arcadedb_embedded_native-26.10.2.dev1.dist-info/licenses/licenses/Apache-2.0.txt +73 -0
  8. arcadedb_embedded_native-26.10.2.dev1.dist-info/licenses/licenses/GraalVM-CE-LICENSE.txt +430 -0
  9. arcadedb_embedded_native-26.10.2.dev1.dist-info/licenses/licenses/GraalVM-THIRD_PARTY_LICENSE.txt +7357 -0
  10. arcadedb_embedded_native-26.10.2.dev1.dist-info/licenses/licenses/GraalVM-native-image-LICENSE.txt +379 -0
  11. arcadedb_embedded_native-26.10.2.dev1.dist-info/top_level.txt +1 -0
  12. arcadedb_native/__init__.py +128 -0
  13. arcadedb_native/_build_info.py +5 -0
  14. arcadedb_native/_jpype_stub.py +44 -0
  15. arcadedb_native/_logging.py +23 -0
  16. arcadedb_native/_native/__init__.py +31 -0
  17. arcadedb_native/_native/bulk.py +96 -0
  18. arcadedb_native/_native/codec.py +229 -0
  19. arcadedb_native/_native/columns.py +167 -0
  20. arcadedb_native/_native/database.py +1356 -0
  21. arcadedb_native/_native/extras.py +819 -0
  22. arcadedb_native/_native/lib.py +338 -0
  23. arcadedb_native/_native/libarcadedb.dll +4 -0
  24. arcadedb_native/_native/records.py +204 -0
  25. arcadedb_native/_native/schema_objects.py +379 -0
  26. arcadedb_native/_native/server.py +118 -0
  27. arcadedb_native/async_executor.py +1114 -0
  28. arcadedb_native/core.py +2189 -0
  29. arcadedb_native/dataframe.py +479 -0
  30. arcadedb_native/exceptions.py +147 -0
  31. arcadedb_native/exporter.py +309 -0
  32. arcadedb_native/graph.py +302 -0
  33. arcadedb_native/graph_batch.py +527 -0
  34. arcadedb_native/importer.py +284 -0
  35. arcadedb_native/jvm.py +632 -0
  36. arcadedb_native/results.py +1369 -0
  37. arcadedb_native/schema.py +758 -0
  38. arcadedb_native/server.py +347 -0
  39. arcadedb_native/transactions.py +38 -0
  40. arcadedb_native/type_conversion.py +707 -0
  41. arcadedb_native/vector.py +800 -0
@@ -0,0 +1,96 @@
1
+ """Columnar bulk insert through one crossing (``arcadedb_n_insert_cols``): numpy int/float/bool columns and float32 vector
2
+ columns (a 2-D array or a list of equal-length 1-D float arrays) go over as one buffer; anything else returns None and the
3
+ caller falls back to the per-row path. The documents are built Java-side as ``DocumentBatcher.insertColumns`` builds them on JPype.
4
+ """
5
+
6
+ import json
7
+ import os
8
+
9
+ from . import lib
10
+
11
+ try:
12
+ import numpy as _np
13
+ except ImportError: # pragma: no cover
14
+ _np = None
15
+
16
+ _ENABLED = os.environ.get("ARCADEDB_NATIVE_BULK", "1") != "0"
17
+
18
+
19
+ def insert_columns_fast(db, type_name, columns, commit_every):
20
+ if (
21
+ not _ENABLED
22
+ or _np is None
23
+ or lib._lib is None
24
+ or not hasattr(lib._lib, "arcadedb_n_insert_cols")
25
+ ):
26
+ return None
27
+ cols, parts, n = [], [], None
28
+ for name, values in columns.items():
29
+ if hasattr(values, "to_numpy") and not isinstance(values, (list, tuple)):
30
+ return None
31
+ kind, dim = None, 0
32
+ if isinstance(values, _np.ndarray):
33
+ k = values.dtype.kind
34
+ if (
35
+ values.ndim == 1
36
+ and k in "iu"
37
+ and (
38
+ k == "i"
39
+ or not values.size
40
+ or int(values.max()) <= _np.iinfo(_np.int64).max
41
+ )
42
+ ):
43
+ kind, arr = "q", _np.ascontiguousarray(values, dtype=_np.int64)
44
+ elif values.ndim == 1 and k == "f":
45
+ kind, arr = "d", _np.ascontiguousarray(values, dtype=_np.float64)
46
+ elif values.ndim == 1 and k == "b":
47
+ kind, arr = "b", _np.ascontiguousarray(values, dtype=_np.uint8)
48
+ elif values.ndim == 2 and k == "f" and values.shape[1] > 0:
49
+ kind, arr, dim = (
50
+ "f",
51
+ _np.ascontiguousarray(values, dtype=_np.float32),
52
+ int(values.shape[1]),
53
+ )
54
+ elif (
55
+ isinstance(values, (list, tuple))
56
+ and values
57
+ and all(
58
+ isinstance(v, _np.ndarray)
59
+ and v.ndim == 1
60
+ and v.dtype.kind == "f"
61
+ and v.shape == values[0].shape
62
+ for v in values
63
+ )
64
+ and values[0].shape[0] > 0
65
+ ):
66
+ kind, arr, dim = (
67
+ "f",
68
+ _np.ascontiguousarray(_np.stack(values), dtype=_np.float32),
69
+ int(values[0].shape[0]),
70
+ )
71
+ if kind is None:
72
+ return None
73
+ rows = int(arr.shape[0])
74
+ if n is not None and rows != n:
75
+ return None # the per-row path reports the ragged columns
76
+ n = rows
77
+ cols.append([name, kind, dim])
78
+ parts.append(arr.tobytes())
79
+ if not n:
80
+ return None
81
+ header = json.dumps(
82
+ {"n": n, "commit": min(int(commit_every), 2**31 - 1), "cols": cols}
83
+ )
84
+ buf = b"".join(parts)
85
+ from .database import _call
86
+
87
+ reply = _call(
88
+ "Failed to bulk-insert",
89
+ "arcadedb_n_insert_cols",
90
+ db._h,
91
+ lib.b(type_name),
92
+ lib.b(header),
93
+ buf,
94
+ len(buf),
95
+ )
96
+ return int(reply["n"])
@@ -0,0 +1,229 @@
1
+ """Python <-> native JSON: query parameters out, typed rows in."""
2
+
3
+ import json
4
+ import os
5
+ from collections.abc import Mapping
6
+ from datetime import date, datetime, timezone
7
+ from decimal import Decimal
8
+
9
+ try: # numpy is optional
10
+ import numpy as _np
11
+ except ImportError: # pragma: no cover
12
+ _np = None
13
+
14
+ TAG = "\u0001"
15
+ _EPOCH_NAIVE = datetime(1970, 1, 1)
16
+
17
+
18
+ def _default(value, _bin=None):
19
+ """json.dumps hook: the values JSON cannot carry, as tagged objects (see Json.java).
20
+
21
+ ``_bin`` (binary mode): ``[arrays, floats_so_far]``; a float ndarray then goes to the side buffer as float32 and the JSON carries
22
+ ``["B", offset, count]`` instead of the number list (no float formatting in Python, no text parsing in Java).
23
+ """
24
+ if isinstance(value, RID):
25
+ return {TAG: ["R", value.text]}
26
+ if isinstance(value, Decimal):
27
+ return {TAG: ["n", str(value)]}
28
+ if isinstance(value, datetime):
29
+ utc = (
30
+ value.astimezone(timezone.utc).replace(tzinfo=None)
31
+ if value.utcoffset() is not None
32
+ else value
33
+ )
34
+ return {TAG: ["T", utc.isoformat()]}
35
+ if isinstance(value, date):
36
+ return {TAG: ["D", value.isoformat()]}
37
+ if isinstance(value, (set, frozenset)):
38
+ return {TAG: ["s", list(value)]}
39
+ if isinstance(value, (bytes, bytearray, memoryview)):
40
+ import base64
41
+
42
+ return {TAG: ["b", base64.b64encode(bytes(value)).decode("ascii")]}
43
+ if _np is not None:
44
+ if isinstance(value, _np.ndarray):
45
+ if value.dtype.kind == "f":
46
+ if _bin is not None:
47
+ arr = _np.ascontiguousarray(value.ravel(), dtype=_np.float32)
48
+ off = _bin[1]
49
+ _bin[0].append(arr)
50
+ _bin[1] = off + arr.size
51
+ return {TAG: ["B", off, arr.size]}
52
+ return {TAG: ["F", value.tolist()]}
53
+ return value.tolist()
54
+ if isinstance(value, _np.bool_):
55
+ return bool(value)
56
+ if isinstance(value, _np.integer):
57
+ return int(value)
58
+ if isinstance(value, _np.floating):
59
+ return float(value)
60
+ if isinstance(value, Mapping):
61
+ return dict(value)
62
+ raise TypeError(
63
+ f"cannot pass {type(value).__name__} as a parameter on the native backend"
64
+ )
65
+
66
+
67
+ _PLAIN = frozenset((str, int, float, bool, type(None)))
68
+ _dumps = json.dumps
69
+ _fast = os.environ.get("ARCADEDB_NATIVE_FASTENC", "1") != "0"
70
+
71
+
72
+ def _prep(value):
73
+ """A value ready for json.dumps: a dict with a key that is not a str becomes a tagged ``[[k, v], ..]`` map (JSON would turn
74
+ the key into text; the JPype path keeps it), and containers are walked for such dicts. Plain scalars pass untouched.
75
+ """
76
+ t = type(value)
77
+ if t in _PLAIN:
78
+ return value
79
+ if isinstance(value, Mapping):
80
+ if all(type(k) is str for k in value):
81
+ return {k: _prep(v) for k, v in value.items()}
82
+ return {TAG: ["m", [[_prep(k), _prep(v)] for k, v in value.items()]]}
83
+ if t is list or t is tuple:
84
+ return [_prep(v) for v in value]
85
+ return value
86
+
87
+
88
+ def encode_params(args, binary=False):
89
+ """The ``params`` text for ``arcadedb_n_exec``: '' for none, else ``{"named":{..}}`` or ``{"pos":[..]}``.
90
+
91
+ Same selection as the JPype path: a lone list/tuple is the positional list itself, a lone mapping (also alone in
92
+ a list) is the named map, anything else is positional.
93
+ """
94
+ if not args:
95
+ return ("", None, 0) if binary else ""
96
+ values = args[0] if len(args) == 1 and isinstance(args[0], (list, tuple)) else args
97
+ plain = _PLAIN
98
+ if _fast and set(map(type, values)) <= plain:
99
+ # all plain scalars (the per-row INSERT of a load): the cached default encoder, no default hook, no per-call encoder object
100
+ text = _dumps({"pos": values if type(values) is list else list(values)})
101
+ return (text, None, 0) if binary else text
102
+ if len(values) == 1 and isinstance(values[0], Mapping):
103
+ named = values[0]
104
+ if not all(type(v) in plain for v in named.values()):
105
+ named = {k: _prep(v) for k, v in named.items()}
106
+ return _dump({"named": dict(named)}, binary)
107
+ if not all(type(v) in plain for v in values):
108
+ values = [_prep(v) for v in values]
109
+ return _dump({"pos": list(values)}, binary)
110
+
111
+
112
+ def _dump(obj, binary):
113
+ """JSON text; binary mode returns ``(text, float32 side buffer bytes or None, float count)``."""
114
+ if not binary:
115
+ return json.dumps(obj, default=_default)
116
+ state = [[], 0]
117
+ text = json.dumps(obj, default=lambda v: _default(v, state))
118
+ arrays = state[0]
119
+ if not arrays:
120
+ return text, None, 0
121
+ buf = arrays[0].tobytes() if len(arrays) == 1 else _np.concatenate(arrays).tobytes()
122
+ return text, buf, state[1]
123
+
124
+
125
+ class RID:
126
+ """A record id as the engine returns it in a row; passed back as a parameter it is a link, not a string."""
127
+
128
+ __slots__ = ("text",)
129
+
130
+ def __init__(self, text):
131
+ self.text = text
132
+
133
+ def __str__(self):
134
+ return self.text
135
+
136
+ def __repr__(self):
137
+ return f"RID({self.text!r})"
138
+
139
+ def __eq__(self, other):
140
+ return self.text == (other.text if isinstance(other, RID) else other)
141
+
142
+ def __hash__(self):
143
+ return hash(self.text)
144
+
145
+
146
+ def _restore_float(text):
147
+ return float(text)
148
+
149
+
150
+ def _restore_map(pairs):
151
+ return {k: v for k, v in pairs}
152
+
153
+
154
+ _SIMPLE = {
155
+ "n": Decimal,
156
+ "i": int,
157
+ "D": date.fromisoformat,
158
+ "T": datetime.fromisoformat,
159
+ "Z": datetime.fromisoformat,
160
+ "s": set,
161
+ "f": _restore_float,
162
+ "m": _restore_map,
163
+ "R": RID,
164
+ }
165
+
166
+
167
+ def _hook(obj):
168
+ tagged = obj.get(TAG)
169
+ # a column may be called like the tag: only a lone [kind, payload] pair of a known kind is a tagged value
170
+ if (
171
+ tagged is None
172
+ or len(obj) != 1
173
+ or type(tagged) is not list
174
+ or len(tagged) != 2
175
+ or tagged[0] not in _SIMPLE
176
+ ):
177
+ return obj
178
+ kind, payload = tagged
179
+ return _SIMPLE[kind](payload)
180
+
181
+
182
+ def loads(text: str, floats=None):
183
+ """Decode a reply, restoring tagged values. ``floats``: the float32 tail of a binary reply (``["B", offset, count]`` values)."""
184
+ if floats is None:
185
+ return json.loads(text, object_hook=_hook)
186
+
187
+ def hook(obj):
188
+ tagged = obj.get(TAG)
189
+ if (
190
+ tagged is not None
191
+ and len(obj) == 1
192
+ and type(tagged) is list
193
+ and len(tagged) == 3
194
+ and tagged[0] == "B"
195
+ ):
196
+ return floats[
197
+ tagged[1] : tagged[1] + tagged[2]
198
+ ].tolist() # float32 widens to the same double as the JPype path
199
+ return _hook(obj)
200
+
201
+ return json.loads(text, object_hook=hook)
202
+
203
+
204
+ # --- JSON-native form of a row (ResultSet.to_json_list): DATE/DATETIME as epoch millis, DECIMAL as float.
205
+
206
+
207
+ def json_native(value):
208
+ if isinstance(value, datetime):
209
+ return (
210
+ int((value.replace(tzinfo=None) - _EPOCH_NAIVE).total_seconds() * 1000)
211
+ if value.tzinfo is None
212
+ else int(value.timestamp() * 1000)
213
+ )
214
+ if isinstance(value, date):
215
+ return int(
216
+ (
217
+ datetime.combine(value, datetime.min.time()) - _EPOCH_NAIVE
218
+ ).total_seconds()
219
+ * 1000
220
+ )
221
+ if isinstance(value, Decimal):
222
+ return float(value)
223
+ if isinstance(value, (set, frozenset)):
224
+ return [json_native(v) for v in value]
225
+ if isinstance(value, list):
226
+ return [json_native(v) for v in value]
227
+ if isinstance(value, dict):
228
+ return {k: json_native(v) for k, v in value.items()}
229
+ return value
@@ -0,0 +1,167 @@
1
+ """Rows to columns for the native backend (the columnar path is Python over the rows; the JPype path decodes a Java buffer)."""
2
+
3
+ from datetime import date, datetime, timezone
4
+ from decimal import Decimal
5
+
6
+
7
+ def _names(rows, columns):
8
+ if columns is not None:
9
+ return list(columns)
10
+ seen = {}
11
+ for row in rows:
12
+ for k in row:
13
+ seen.setdefault(k, None)
14
+ return list(seen)
15
+
16
+
17
+ def _kind(values):
18
+ """'b', 'i', 'f', 'dt', 'dec', 'obj' for the non-null values of a column."""
19
+ kinds = set()
20
+ for v in values:
21
+ if v is None:
22
+ continue
23
+ t = type(v)
24
+ if t is bool:
25
+ kinds.add("b")
26
+ elif t is int:
27
+ kinds.add("i")
28
+ elif t is float:
29
+ kinds.add("f")
30
+ elif t is datetime or t is date:
31
+ kinds.add("dt")
32
+ elif t is Decimal:
33
+ kinds.add("dec")
34
+ else:
35
+ kinds.add("obj")
36
+ if not kinds:
37
+ return "obj"
38
+ if len(kinds) == 1:
39
+ return kinds.pop()
40
+ if kinds == {"i", "f"}:
41
+ return "f"
42
+ return "obj"
43
+
44
+
45
+ def _ms(v):
46
+ if isinstance(v, datetime):
47
+ if v.tzinfo is not None:
48
+ v = v.astimezone(timezone.utc).replace(tzinfo=None)
49
+ return v
50
+ return datetime(v.year, v.month, v.day)
51
+
52
+
53
+ def _float_vectors(values):
54
+ """A column of equal-length float lists (None allowed) that float32 holds exactly, as a (count, dim) float32 array with
55
+ NaN rows for the nulls; None for any other column. Rows cannot say float[] from List<Double>, so only a column whose every
56
+ value survives float32 unchanged is read as a vector column (the JPype path decides on the Java element type).
57
+ """
58
+ import numpy as np
59
+
60
+ dim = 0
61
+ for v in values:
62
+ if v is None:
63
+ continue
64
+ if type(v) is not list or not v or not all(type(x) is float for x in v):
65
+ return None
66
+ if dim == 0:
67
+ dim = len(v)
68
+ elif len(v) != dim:
69
+ return None
70
+ if dim == 0:
71
+ return None
72
+ arr = np.full((len(values), dim), np.nan, dtype=np.float32)
73
+ for i, v in enumerate(values):
74
+ if v is not None:
75
+ arr[i] = v
76
+ if not np.array_equal(arr[i].astype(np.float64), np.array(v)):
77
+ return None
78
+ return arr
79
+
80
+
81
+ def to_columns(rows, columns=None):
82
+ import numpy as np
83
+
84
+ out = {}
85
+ for name in _names(rows, columns):
86
+ values = [row.get(name) for row in rows]
87
+ if _kind(values) == "obj":
88
+ vectors = _float_vectors(values)
89
+ if vectors is not None:
90
+ out[name] = vectors
91
+ continue
92
+ has_null = any(v is None for v in values)
93
+ kind = _kind(values)
94
+ if kind == "i":
95
+ out[name] = (
96
+ np.array([np.nan if v is None else v for v in values], dtype=np.float64)
97
+ if has_null
98
+ else np.array(values, dtype=np.int64)
99
+ )
100
+ elif kind == "f":
101
+ out[name] = np.array(
102
+ [np.nan if v is None else v for v in values], dtype=np.float64
103
+ )
104
+ elif kind == "b":
105
+ out[name] = values if has_null else np.array(values, dtype=bool)
106
+ elif kind == "dt":
107
+ out[name] = np.array(
108
+ [
109
+ (
110
+ np.datetime64("NaT", "ms")
111
+ if v is None
112
+ else np.datetime64(_ms(v), "ms")
113
+ )
114
+ for v in values
115
+ ],
116
+ dtype="datetime64[ms]",
117
+ )
118
+ elif kind == "dec":
119
+ arr = np.empty(len(values), dtype=object)
120
+ arr[:] = values
121
+ out[name] = arr
122
+ else:
123
+ out[name] = values
124
+ return out
125
+
126
+
127
+ def to_arrow(rows, columns=None):
128
+ import pyarrow as pa
129
+
130
+ arrays = {}
131
+ for name in _names(rows, columns):
132
+ values = [row.get(name) for row in rows]
133
+ kind = _kind(values)
134
+ if kind == "i":
135
+ arrays[name] = pa.array(values, type=pa.int64())
136
+ elif kind == "f":
137
+ try:
138
+ arrays[name] = pa.array(values, type=pa.float64())
139
+ except (
140
+ pa.ArrowInvalid
141
+ ): # an int beyond +-2**53 next to floats: the column degrades to exact text
142
+ arrays[name] = pa.array(
143
+ [None if v is None else str(v) for v in values], type=pa.string()
144
+ )
145
+ elif kind == "b":
146
+ arrays[name] = pa.array(values, type=pa.bool_())
147
+ elif kind == "dt":
148
+ arrays[name] = pa.array(
149
+ [None if v is None else _ms(v) for v in values], type=pa.timestamp("ms")
150
+ )
151
+ elif kind == "dec":
152
+ try:
153
+ arrays[name] = pa.array(values)
154
+ except (
155
+ pa.ArrowInvalid
156
+ ): # wider than decimal256 (76 digits): an exact string
157
+ arrays[name] = pa.array(
158
+ [None if v is None else str(v) for v in values], type=pa.string()
159
+ )
160
+ else:
161
+ try:
162
+ arrays[name] = pa.array([None if v is None else v for v in values])
163
+ except (pa.ArrowInvalid, pa.ArrowTypeError):
164
+ arrays[name] = pa.array(
165
+ [None if v is None else str(v) for v in values], type=pa.string()
166
+ )
167
+ return pa.table(arrays)