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.
- arcadedb_embedded_native-26.10.2.dev1.dist-info/METADATA +114 -0
- arcadedb_embedded_native-26.10.2.dev1.dist-info/RECORD +41 -0
- arcadedb_embedded_native-26.10.2.dev1.dist-info/WHEEL +5 -0
- arcadedb_embedded_native-26.10.2.dev1.dist-info/licenses/LICENSE +201 -0
- arcadedb_embedded_native-26.10.2.dev1.dist-info/licenses/NOTICE +25 -0
- arcadedb_embedded_native-26.10.2.dev1.dist-info/licenses/THIRD_PARTY_NOTICES.md +3629 -0
- arcadedb_embedded_native-26.10.2.dev1.dist-info/licenses/licenses/Apache-2.0.txt +73 -0
- arcadedb_embedded_native-26.10.2.dev1.dist-info/licenses/licenses/GraalVM-CE-LICENSE.txt +430 -0
- arcadedb_embedded_native-26.10.2.dev1.dist-info/licenses/licenses/GraalVM-THIRD_PARTY_LICENSE.txt +7357 -0
- arcadedb_embedded_native-26.10.2.dev1.dist-info/licenses/licenses/GraalVM-native-image-LICENSE.txt +379 -0
- arcadedb_embedded_native-26.10.2.dev1.dist-info/top_level.txt +1 -0
- arcadedb_native/__init__.py +128 -0
- arcadedb_native/_build_info.py +5 -0
- arcadedb_native/_jpype_stub.py +44 -0
- arcadedb_native/_logging.py +23 -0
- arcadedb_native/_native/__init__.py +31 -0
- arcadedb_native/_native/bulk.py +96 -0
- arcadedb_native/_native/codec.py +229 -0
- arcadedb_native/_native/columns.py +167 -0
- arcadedb_native/_native/database.py +1356 -0
- arcadedb_native/_native/extras.py +819 -0
- arcadedb_native/_native/lib.py +338 -0
- arcadedb_native/_native/libarcadedb.dll +4 -0
- arcadedb_native/_native/records.py +204 -0
- arcadedb_native/_native/schema_objects.py +379 -0
- arcadedb_native/_native/server.py +118 -0
- arcadedb_native/async_executor.py +1114 -0
- arcadedb_native/core.py +2189 -0
- arcadedb_native/dataframe.py +479 -0
- arcadedb_native/exceptions.py +147 -0
- arcadedb_native/exporter.py +309 -0
- arcadedb_native/graph.py +302 -0
- arcadedb_native/graph_batch.py +527 -0
- arcadedb_native/importer.py +284 -0
- arcadedb_native/jvm.py +632 -0
- arcadedb_native/results.py +1369 -0
- arcadedb_native/schema.py +758 -0
- arcadedb_native/server.py +347 -0
- arcadedb_native/transactions.py +38 -0
- arcadedb_native/type_conversion.py +707 -0
- 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)
|