jupyter-duckdb 0.9.2.2.dev202401171418__tar.gz → 0.9.2.3__tar.gz
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.
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/PKG-INFO +1 -1
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/setup.py +7 -5
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/db/Connection.py +12 -1
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/db/Table.py +1 -1
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/db/__init__.py +2 -8
- jupyter-duckdb-0.9.2.3/src/duckdb_kernel/db/error/EmptyResultError.py +5 -0
- jupyter-duckdb-0.9.2.3/src/duckdb_kernel/db/error/__init__.py +1 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418/src/duckdb_kernel/db → jupyter-duckdb-0.9.2.3/src/duckdb_kernel/db/implementation}/duckdb/Connection.py +10 -2
- jupyter-duckdb-0.9.2.3/src/duckdb_kernel/db/implementation/postgres/Connection.py +226 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418/src/duckdb_kernel/db → jupyter-duckdb-0.9.2.3/src/duckdb_kernel/db/implementation}/sqlite/Connection.py +9 -2
- jupyter-duckdb-0.9.2.3/src/duckdb_kernel/db/implementation/sqlite/__init__.py +1 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/kernel.py +82 -41
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/parser/elements/RAElement.py +2 -2
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/parser/elements/binary/ConditionalSet.py +2 -2
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/jupyter_duckdb.egg-info/PKG-INFO +1 -1
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/jupyter_duckdb.egg-info/SOURCES.txt +8 -4
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/README.md +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/setup.cfg +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/__init__.py +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/__main__.py +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/db/Column.py +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/db/Constraint.py +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/db/DatabaseError.py +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/db/ForeignKey.py +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418/src/duckdb_kernel/db → jupyter-duckdb-0.9.2.3/src/duckdb_kernel/db/implementation}/duckdb/__init__.py +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418/src/duckdb_kernel/db/sqlite → jupyter-duckdb-0.9.2.3/src/duckdb_kernel/db/implementation/postgres}/__init__.py +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/kernel.json +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/magics/MagicCommand.py +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/magics/MagicCommandCallback.py +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/magics/MagicCommandException.py +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/magics/MagicCommandHandler.py +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/magics/__init__.py +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/parser/DCParser.py +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/parser/LogicParser.py +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/parser/RAParser.py +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/parser/__init__.py +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/parser/elements/DCOperand.py +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/parser/elements/LogicElement.py +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/parser/elements/LogicOperand.py +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/parser/elements/LogicOperator.py +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/parser/elements/RABinaryOperator.py +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/parser/elements/RAOperand.py +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/parser/elements/RAOperator.py +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/parser/elements/RAUnaryOperator.py +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/parser/elements/__init__.py +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/parser/elements/binary/Add.py +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/parser/elements/binary/And.py +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/parser/elements/binary/ArrowLeft.py +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/parser/elements/binary/Cross.py +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/parser/elements/binary/Difference.py +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/parser/elements/binary/Divide.py +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/parser/elements/binary/Equal.py +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/parser/elements/binary/GreaterThan.py +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/parser/elements/binary/GreaterThanEqual.py +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/parser/elements/binary/Intersection.py +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/parser/elements/binary/Join.py +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/parser/elements/binary/LessThan.py +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/parser/elements/binary/LessThanEqual.py +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/parser/elements/binary/Minus.py +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/parser/elements/binary/Multiply.py +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/parser/elements/binary/Or.py +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/parser/elements/binary/Unequal.py +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/parser/elements/binary/Union.py +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/parser/elements/binary/__init__.py +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/parser/elements/unary/Not.py +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/parser/elements/unary/Projection.py +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/parser/elements/unary/Rename.py +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/parser/elements/unary/Selection.py +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/parser/elements/unary/__init__.py +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/parser/tokenizer/Token.py +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/parser/tokenizer/Tokenizer.py +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/parser/tokenizer/__init__.py +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/parser/util/RenamableColumn.py +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/parser/util/RenamableColumnList.py +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/parser/util/__init__.py +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/util/ResultSetComparator.py +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/util/__init__.py +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/util/formatting.py +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/visualization/Drawer.py +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/visualization/RATreeDrawer.py +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/visualization/SchemaDrawer.py +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/visualization/__init__.py +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/jupyter_duckdb.egg-info/dependency_links.txt +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/jupyter_duckdb.egg-info/requires.txt +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/jupyter_duckdb.egg-info/top_level.txt +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/test/test_dc.py +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/test/test_ra.py +0 -0
- {jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/test/test_result_comparison.py +0 -0
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import os
|
|
2
|
+
import re
|
|
2
3
|
from datetime import datetime
|
|
3
4
|
|
|
4
5
|
from setuptools import setup, find_packages
|
|
5
6
|
|
|
6
7
|
# version
|
|
7
|
-
version = '0
|
|
8
|
-
|
|
9
|
-
if os.getenv('DEV') == '1':
|
|
10
|
-
version += '.dev' + datetime.now().strftime('%Y%m%d%H%M')
|
|
8
|
+
version = os.getenv('PACKAGE_VERSION', '1.0')
|
|
9
|
+
version = re.sub(r'[^dev0-9._]', '', version)
|
|
11
10
|
|
|
12
11
|
# install requires
|
|
13
12
|
install_requires = [
|
|
@@ -47,7 +46,10 @@ setup(
|
|
|
47
46
|
install_requires=install_requires,
|
|
48
47
|
package_data={
|
|
49
48
|
'duckdb_kernel': [
|
|
50
|
-
'kernel.json'
|
|
49
|
+
'kernel.json',
|
|
50
|
+
'db/implementation/duckdb/*.py',
|
|
51
|
+
'db/implementation/postgres/*.py',
|
|
52
|
+
'db/implementation/sqlite/*.py'
|
|
51
53
|
]
|
|
52
54
|
},
|
|
53
55
|
include_package_data=True
|
{jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/db/Connection.py
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from typing import Dict, List, Tuple, Any
|
|
2
2
|
|
|
3
|
-
from . import Table
|
|
3
|
+
from .Table import Table
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
class Connection:
|
|
@@ -10,6 +10,17 @@ class Connection:
|
|
|
10
10
|
def close(self):
|
|
11
11
|
pass
|
|
12
12
|
|
|
13
|
+
@staticmethod
|
|
14
|
+
def plain_explain() -> bool:
|
|
15
|
+
return False
|
|
16
|
+
|
|
17
|
+
@staticmethod
|
|
18
|
+
def multiple_statements_per_query() -> bool:
|
|
19
|
+
return True
|
|
20
|
+
|
|
21
|
+
def __str__(self) -> str:
|
|
22
|
+
raise NotImplementedError
|
|
23
|
+
|
|
13
24
|
def execute(self, query: str) -> Tuple[List[str], List[List[Any]]]:
|
|
14
25
|
raise NotImplementedError
|
|
15
26
|
|
{jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/db/__init__.py
RENAMED
|
@@ -1,12 +1,6 @@
|
|
|
1
|
-
from .DatabaseError import DatabaseError
|
|
2
1
|
from .Column import Column
|
|
2
|
+
from .Connection import Connection
|
|
3
3
|
from .Constraint import Constraint
|
|
4
|
+
from .DatabaseError import DatabaseError
|
|
4
5
|
from .ForeignKey import ForeignKey
|
|
5
6
|
from .Table import Table
|
|
6
|
-
|
|
7
|
-
try:
|
|
8
|
-
from .duckdb import Connection
|
|
9
|
-
SQLITE_MODE = False
|
|
10
|
-
except ImportError:
|
|
11
|
-
from .sqlite import Connection
|
|
12
|
-
SQLITE_MODE = True
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .EmptyResultError import EmptyResultError
|
|
@@ -2,17 +2,25 @@ from typing import Dict, List, Tuple, Any
|
|
|
2
2
|
|
|
3
3
|
import duckdb
|
|
4
4
|
|
|
5
|
-
from
|
|
6
|
-
from
|
|
5
|
+
from ... import DatabaseError, Column, Constraint, ForeignKey, Table
|
|
6
|
+
from ...Connection import Connection as Base
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class Connection(Base):
|
|
10
10
|
def __init__(self, path: str):
|
|
11
|
+
self.path: str = path
|
|
11
12
|
self.con: duckdb.DuckDBPyConnection = duckdb.connect(path, read_only=False)
|
|
12
13
|
|
|
13
14
|
def close(self):
|
|
14
15
|
self.con.close()
|
|
15
16
|
|
|
17
|
+
@staticmethod
|
|
18
|
+
def plain_explain() -> bool:
|
|
19
|
+
return True
|
|
20
|
+
|
|
21
|
+
def __str__(self) -> str:
|
|
22
|
+
return f'DuckDB: {self.path}'
|
|
23
|
+
|
|
16
24
|
def execute(self, query: str) -> Tuple[List[str], List[List[Any]]]:
|
|
17
25
|
with self.con.cursor() as cursor:
|
|
18
26
|
try:
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import contextlib
|
|
2
|
+
import re
|
|
3
|
+
from typing import Tuple, List, Any, Optional, Dict
|
|
4
|
+
from uuid import uuid4
|
|
5
|
+
|
|
6
|
+
import psycopg
|
|
7
|
+
|
|
8
|
+
from ... import DatabaseError, Column, Constraint, ForeignKey, Table
|
|
9
|
+
from ...Connection import Connection as Base
|
|
10
|
+
from ...error import EmptyResultError
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Connection(Base):
|
|
14
|
+
def __init__(self,
|
|
15
|
+
host: str, port: int,
|
|
16
|
+
username: Optional[str], password: Optional[str],
|
|
17
|
+
database_name: Optional[str]):
|
|
18
|
+
self.host: str = host
|
|
19
|
+
self.port: int = port
|
|
20
|
+
self.username: Optional[str] = username
|
|
21
|
+
|
|
22
|
+
options = {
|
|
23
|
+
'host': host,
|
|
24
|
+
'port': port,
|
|
25
|
+
'user': username,
|
|
26
|
+
'password': password,
|
|
27
|
+
'autocommit': True
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
# If not database name is provided we create one.
|
|
31
|
+
if database_name is None:
|
|
32
|
+
# connect to the server without a database name
|
|
33
|
+
with contextlib.closing(psycopg.connect(**options, dbname='postgres')) as temp_con:
|
|
34
|
+
# create a database to use
|
|
35
|
+
database_name: str = 'db_' + str(uuid4()).replace('-', '')
|
|
36
|
+
temp_con.execute(f'CREATE DATABASE {database_name}')
|
|
37
|
+
|
|
38
|
+
# Finally the "real" connection is created using the database name.
|
|
39
|
+
self.con = psycopg.connect(**options, dbname=database_name)
|
|
40
|
+
self.database_name: str = database_name
|
|
41
|
+
|
|
42
|
+
def close(self):
|
|
43
|
+
self.con.close()
|
|
44
|
+
|
|
45
|
+
def __str__(self) -> str:
|
|
46
|
+
user = f'{self.username}@' if self.username is not None else ''
|
|
47
|
+
return f'PostgreSQL: {user}{self.host}:{self.port}/{self.database_name}'
|
|
48
|
+
|
|
49
|
+
def execute(self, query: str) -> Tuple[List[str], List[List[Any]]]:
|
|
50
|
+
with self.con.cursor() as cursor:
|
|
51
|
+
try:
|
|
52
|
+
cursor.execute(query)
|
|
53
|
+
except Exception as e:
|
|
54
|
+
if isinstance(e, psycopg.Error):
|
|
55
|
+
text = str(e)
|
|
56
|
+
raise DatabaseError(text)
|
|
57
|
+
else:
|
|
58
|
+
raise e
|
|
59
|
+
|
|
60
|
+
# get rows
|
|
61
|
+
try:
|
|
62
|
+
rows = cursor.fetchall()
|
|
63
|
+
except psycopg.ProgrammingError as e:
|
|
64
|
+
text = str(e)
|
|
65
|
+
if text == "the last operation didn't produce a result":
|
|
66
|
+
raise EmptyResultError()
|
|
67
|
+
else:
|
|
68
|
+
raise e
|
|
69
|
+
|
|
70
|
+
# get columns
|
|
71
|
+
if cursor.description is None:
|
|
72
|
+
columns = []
|
|
73
|
+
else:
|
|
74
|
+
columns = [e[0] for e in cursor.description]
|
|
75
|
+
|
|
76
|
+
return columns, rows
|
|
77
|
+
|
|
78
|
+
def analyze(self) -> Dict[str, Table]:
|
|
79
|
+
tables: Dict[str, Table] = {}
|
|
80
|
+
constraints: Dict[str, Constraint] = {}
|
|
81
|
+
constraint_index: int = 0
|
|
82
|
+
|
|
83
|
+
# First, receive the table names.
|
|
84
|
+
for table_name, in self.con.execute('''
|
|
85
|
+
SELECT table_name
|
|
86
|
+
FROM information_schema.tables
|
|
87
|
+
WHERE table_schema='public' AND table_type='BASE TABLE'
|
|
88
|
+
''').fetchall():
|
|
89
|
+
table = Table(table_name)
|
|
90
|
+
tables[table_name] = table
|
|
91
|
+
|
|
92
|
+
# Get column names and data types for each table.
|
|
93
|
+
for table_name, column_name, data_type in self.con.execute('''
|
|
94
|
+
SELECT
|
|
95
|
+
table_name,
|
|
96
|
+
column_name,
|
|
97
|
+
data_type
|
|
98
|
+
FROM information_schema.columns
|
|
99
|
+
ORDER BY ordinal_position ASC
|
|
100
|
+
''').fetchall():
|
|
101
|
+
if table_name in tables:
|
|
102
|
+
table = tables[table_name]
|
|
103
|
+
|
|
104
|
+
column = Column(table, column_name, data_type)
|
|
105
|
+
table.columns.append(column)
|
|
106
|
+
|
|
107
|
+
# Find primary keys.
|
|
108
|
+
constraints_dict = {}
|
|
109
|
+
|
|
110
|
+
for constraint_name, table_name, column_name in self.con.execute('''
|
|
111
|
+
SELECT tc.constraint_name, tc.table_name, c.column_name
|
|
112
|
+
FROM information_schema.table_constraints tc
|
|
113
|
+
JOIN information_schema.constraint_column_usage AS ccu
|
|
114
|
+
USING (constraint_schema, constraint_name)
|
|
115
|
+
JOIN information_schema.columns AS c
|
|
116
|
+
ON c.table_schema = tc.constraint_schema AND tc.table_name = c.table_name AND ccu.column_name = c.column_name
|
|
117
|
+
WHERE tc.table_schema = 'public' AND tc.constraint_type = 'PRIMARY KEY'
|
|
118
|
+
ORDER BY tc.table_name, c.ordinal_position
|
|
119
|
+
'''):
|
|
120
|
+
if constraint_name not in constraints_dict:
|
|
121
|
+
constraints_dict[constraint_name] = (table_name, [])
|
|
122
|
+
|
|
123
|
+
constraints_dict[constraint_name][1].append(column_name)
|
|
124
|
+
|
|
125
|
+
for constraint_name, (table_name, column_names) in constraints_dict.items():
|
|
126
|
+
# get table
|
|
127
|
+
if table_name not in tables:
|
|
128
|
+
raise AssertionError(f'unknown table {table_name} for constraint {constraint_index}')
|
|
129
|
+
|
|
130
|
+
table = tables[table_name]
|
|
131
|
+
|
|
132
|
+
# store constraint
|
|
133
|
+
constraint = Constraint(
|
|
134
|
+
constraint_index,
|
|
135
|
+
table,
|
|
136
|
+
tuple(table.get_column(c) for c in column_names)
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
constraint_index += 1
|
|
140
|
+
|
|
141
|
+
# store key
|
|
142
|
+
if table.primary_key is not None:
|
|
143
|
+
raise AssertionError(f'discovered second primary key for table {table_name}')
|
|
144
|
+
|
|
145
|
+
table.primary_key = constraint
|
|
146
|
+
|
|
147
|
+
# Find unqiue keys.
|
|
148
|
+
constraints_dict = {}
|
|
149
|
+
|
|
150
|
+
for constraint_name, table_name, column_name in self.con.execute('''
|
|
151
|
+
SELECT tc.constraint_name, tc.table_name, c.column_name
|
|
152
|
+
FROM information_schema.table_constraints tc
|
|
153
|
+
JOIN information_schema.constraint_column_usage AS ccu
|
|
154
|
+
USING (constraint_schema, constraint_name)
|
|
155
|
+
JOIN information_schema.columns AS c
|
|
156
|
+
ON c.table_schema = tc.constraint_schema AND tc.table_name = c.table_name AND ccu.column_name = c.column_name
|
|
157
|
+
WHERE tc.table_schema = 'public' AND tc.constraint_type = 'UNIQUE'
|
|
158
|
+
ORDER BY tc.table_name, c.ordinal_position
|
|
159
|
+
'''):
|
|
160
|
+
if constraint_name not in constraints_dict:
|
|
161
|
+
constraints_dict[constraint_name] = (table_name, [])
|
|
162
|
+
|
|
163
|
+
constraints_dict[constraint_name][1].append(column_name)
|
|
164
|
+
|
|
165
|
+
for constraint_name, (table_name, column_names) in constraints_dict.items():
|
|
166
|
+
# get table
|
|
167
|
+
if table_name not in tables:
|
|
168
|
+
raise AssertionError(f'unknown table {table_name} for constraint {constraint_index}')
|
|
169
|
+
|
|
170
|
+
table = tables[table_name]
|
|
171
|
+
|
|
172
|
+
# store constraint
|
|
173
|
+
constraint = Constraint(
|
|
174
|
+
constraint_index,
|
|
175
|
+
table,
|
|
176
|
+
tuple(table.get_column(c) for c in column_names)
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
constraint_index += 1
|
|
180
|
+
|
|
181
|
+
# store key
|
|
182
|
+
table.unique_keys.append(constraint)
|
|
183
|
+
|
|
184
|
+
# Find foreign keys.
|
|
185
|
+
for source_table_name, fk_name, fk_def in self.con.execute('''
|
|
186
|
+
SELECT conrelid::regclass, conname, pg_get_constraintdef(oid)
|
|
187
|
+
FROM pg_constraint
|
|
188
|
+
WHERE contype = 'f'
|
|
189
|
+
AND connamespace = 'public'::regnamespace
|
|
190
|
+
ORDER BY conrelid::regclass::text, contype DESC
|
|
191
|
+
'''):
|
|
192
|
+
# extract information
|
|
193
|
+
match = re.match(r'^FOREIGN KEY \((.*?)\) REFERENCES (.*?)\((.*?)\)', fk_def, re.IGNORECASE)
|
|
194
|
+
if match is None:
|
|
195
|
+
raise AssertionError(f'could not parse foreign key definitions for table {source_table_name}')
|
|
196
|
+
|
|
197
|
+
source_table_column_names = [c.strip() for c in match.group(1).split(',')]
|
|
198
|
+
target_table_name = match.group(2).strip()
|
|
199
|
+
target_table_column_names = [c.strip() for c in match.group(3).split(',')]
|
|
200
|
+
|
|
201
|
+
# get tables
|
|
202
|
+
if source_table_name not in tables:
|
|
203
|
+
raise AssertionError(f'unknown table {source_table_name} for foreign key {fk_name}')
|
|
204
|
+
|
|
205
|
+
source_table = tables[source_table_name]
|
|
206
|
+
|
|
207
|
+
if target_table_name not in tables:
|
|
208
|
+
raise AssertionError(f'unknown table {target_table_name} for foreign key {fk_name}')
|
|
209
|
+
|
|
210
|
+
target_table = tables[target_table_name]
|
|
211
|
+
|
|
212
|
+
# store constraint
|
|
213
|
+
constraint = Constraint(
|
|
214
|
+
constraint_index,
|
|
215
|
+
target_table,
|
|
216
|
+
tuple(target_table.get_column(c) for c in target_table_column_names)
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
constraint_index += 1
|
|
220
|
+
|
|
221
|
+
# store key
|
|
222
|
+
key = ForeignKey(tuple(source_table.get_column(c) for c in source_table_column_names), constraint)
|
|
223
|
+
source_table.foreign_keys.append(key)
|
|
224
|
+
|
|
225
|
+
# return result
|
|
226
|
+
return tables
|
|
@@ -2,8 +2,8 @@ import sqlite3
|
|
|
2
2
|
from contextlib import closing
|
|
3
3
|
from typing import Dict, List, Tuple, Any
|
|
4
4
|
|
|
5
|
-
from
|
|
6
|
-
from
|
|
5
|
+
from ... import DatabaseError, Column, Constraint, ForeignKey, Table
|
|
6
|
+
from ...Connection import Connection as Base
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class Connection(Base):
|
|
@@ -14,6 +14,13 @@ class Connection(Base):
|
|
|
14
14
|
def close(self):
|
|
15
15
|
self.con.close()
|
|
16
16
|
|
|
17
|
+
@staticmethod
|
|
18
|
+
def multiple_statements_per_query() -> bool:
|
|
19
|
+
return False
|
|
20
|
+
|
|
21
|
+
def __str__(self) -> str:
|
|
22
|
+
return f'SQLite: {self.path}'
|
|
23
|
+
|
|
17
24
|
def execute(self, query: str) -> Tuple[List[str], List[List[Any]]]:
|
|
18
25
|
with closing(self.con.cursor()) as cursor:
|
|
19
26
|
try:
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .Connection import Connection
|
{jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/kernel.py
RENAMED
|
@@ -8,7 +8,8 @@ from typing import Optional, Dict, List, Tuple
|
|
|
8
8
|
|
|
9
9
|
from ipykernel.kernelbase import Kernel
|
|
10
10
|
|
|
11
|
-
from .db import Connection, DatabaseError
|
|
11
|
+
from .db import Connection, DatabaseError
|
|
12
|
+
from .db.error import *
|
|
12
13
|
from .magics import *
|
|
13
14
|
from .parser import RAParser, DCParser
|
|
14
15
|
from .util.ResultSetComparator import ResultSetComparator
|
|
@@ -20,7 +21,7 @@ class DuckDBKernel(Kernel):
|
|
|
20
21
|
DEFAULT_MAX_ROWS = 20
|
|
21
22
|
|
|
22
23
|
implementation = 'DuckDB'
|
|
23
|
-
implementation_version = '0
|
|
24
|
+
implementation_version = '1.0'
|
|
24
25
|
banner = 'DuckDB Kernel'
|
|
25
26
|
language_info = {
|
|
26
27
|
'name': 'duckdb',
|
|
@@ -87,7 +88,41 @@ class DuckDBKernel(Kernel):
|
|
|
87
88
|
# database related functions
|
|
88
89
|
def _load_database(self, path: str):
|
|
89
90
|
if self._db is None:
|
|
90
|
-
|
|
91
|
+
# If the provided path looks like a postgres url,
|
|
92
|
+
# we want to use the postgres driver.
|
|
93
|
+
if path.startswith(('postgresql://', 'postgres://', 'pgsql://', 'psql://', 'pg://')):
|
|
94
|
+
# pull data from connection string
|
|
95
|
+
match = re.fullmatch(r'(postgresql|postgres|pgsql|psql|pg)://((.*?)(:(.*?))?@)?(.*?)(:(\d+))?(/(.*))?', path)
|
|
96
|
+
|
|
97
|
+
host = match.group(6)
|
|
98
|
+
port = int(match.group(8)) if match.group(8) is not None else 5432
|
|
99
|
+
username = match.group(3)
|
|
100
|
+
password = match.group(5)
|
|
101
|
+
database_name = match.group(10)
|
|
102
|
+
|
|
103
|
+
# load and create instance
|
|
104
|
+
try:
|
|
105
|
+
from .db.implementation.postgres import Connection as Postgres
|
|
106
|
+
self._db = Postgres(host, port, username, password, database_name)
|
|
107
|
+
except ImportError:
|
|
108
|
+
self.print('psycopg could not be found', name='stderr')
|
|
109
|
+
|
|
110
|
+
# Otherwise the provided path is used to create an
|
|
111
|
+
# in-process instance.
|
|
112
|
+
else:
|
|
113
|
+
# By default, we try to load DuckDB.
|
|
114
|
+
try:
|
|
115
|
+
from .db.implementation.duckdb import Connection as DuckDB
|
|
116
|
+
self._db = DuckDB(path)
|
|
117
|
+
|
|
118
|
+
# If DuckDB is not installed or fails to load,
|
|
119
|
+
# we use SQLite instead.
|
|
120
|
+
except ImportError:
|
|
121
|
+
self.print('DuckDB is not available\n')
|
|
122
|
+
|
|
123
|
+
from .db.implementation.sqlite import Connection as SQLite
|
|
124
|
+
self._db = SQLite(path)
|
|
125
|
+
|
|
91
126
|
return True
|
|
92
127
|
else:
|
|
93
128
|
return False
|
|
@@ -107,7 +142,12 @@ class DuckDBKernel(Kernel):
|
|
|
107
142
|
|
|
108
143
|
# execute query and store start and end timestamp
|
|
109
144
|
st = time.time()
|
|
110
|
-
|
|
145
|
+
|
|
146
|
+
try:
|
|
147
|
+
columns, rows = self._db.execute(query)
|
|
148
|
+
except EmptyResultError:
|
|
149
|
+
columns, rows = None, None
|
|
150
|
+
|
|
111
151
|
et = time.time()
|
|
112
152
|
|
|
113
153
|
# return result if silent
|
|
@@ -115,7 +155,7 @@ class DuckDBKernel(Kernel):
|
|
|
115
155
|
return columns, rows
|
|
116
156
|
|
|
117
157
|
# print EXPLAIN queries as raw text if using DuckDB
|
|
118
|
-
if query.strip().startswith('EXPLAIN') and
|
|
158
|
+
if query.strip().startswith('EXPLAIN') and self._db.plain_explain():
|
|
119
159
|
for ekey, evalue in rows:
|
|
120
160
|
self.print_data(f'<b>{ekey}</b><br><pre>{evalue}</pre>')
|
|
121
161
|
|
|
@@ -123,34 +163,38 @@ class DuckDBKernel(Kernel):
|
|
|
123
163
|
|
|
124
164
|
# print every other query as a table
|
|
125
165
|
else:
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
<
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
166
|
+
if columns is not None:
|
|
167
|
+
# table header
|
|
168
|
+
table_header = ''.join(f'<th>{c}</th>' for c in columns)
|
|
169
|
+
|
|
170
|
+
# table data
|
|
171
|
+
if max_rows is not None and len(rows) > max_rows:
|
|
172
|
+
table_data = f'''
|
|
173
|
+
{rows_table(rows[:math.ceil(max_rows / 2)])}
|
|
174
|
+
<tr>
|
|
175
|
+
<td colspan="{len(columns)}"
|
|
176
|
+
style="text-align: center"
|
|
177
|
+
title="{row_count(len(rows) - max_rows)} omitted">
|
|
178
|
+
...
|
|
179
|
+
</td>
|
|
180
|
+
</tr>
|
|
181
|
+
{rows_table(rows[-math.floor(max_rows // 2):])}
|
|
182
|
+
'''
|
|
183
|
+
else:
|
|
184
|
+
table_data = rows_table(rows)
|
|
185
|
+
|
|
186
|
+
# send to client
|
|
187
|
+
self.print_data(f'''
|
|
188
|
+
<table class="duckdb-query-result">
|
|
189
|
+
{table_header}
|
|
190
|
+
{table_data}
|
|
191
|
+
</table>
|
|
192
|
+
''')
|
|
193
|
+
|
|
194
|
+
self.print_data(f'{row_count(len(rows))} in {et - st:.3f}s')
|
|
152
195
|
|
|
153
|
-
|
|
196
|
+
else:
|
|
197
|
+
self.print_data(f'statement executed without result in {et - st:.3f}s')
|
|
154
198
|
|
|
155
199
|
return columns, rows
|
|
156
200
|
|
|
@@ -167,12 +211,8 @@ class DuckDBKernel(Kernel):
|
|
|
167
211
|
if not silent:
|
|
168
212
|
self.print('unloaded database\n')
|
|
169
213
|
|
|
170
|
-
#
|
|
171
|
-
|
|
172
|
-
if not SQLITE_MODE:
|
|
173
|
-
self.print(f'{self.implementation} {self.implementation_version}\n')
|
|
174
|
-
else:
|
|
175
|
-
self.print('SQLite (DuckDB is not installed)\n')
|
|
214
|
+
# No user cares about the kernel version,
|
|
215
|
+
# so I removed the printing.
|
|
176
216
|
|
|
177
217
|
# clean path
|
|
178
218
|
if path.startswith(("'", '"')):
|
|
@@ -186,7 +226,8 @@ class DuckDBKernel(Kernel):
|
|
|
186
226
|
|
|
187
227
|
if self._load_database(path):
|
|
188
228
|
if not silent:
|
|
189
|
-
self.print(f'loaded database "{path}"\n')
|
|
229
|
+
# self.print(f'loaded database "{path}"\n')
|
|
230
|
+
self.print(str(self._db))
|
|
190
231
|
|
|
191
232
|
# copy data from source database
|
|
192
233
|
if of is not None:
|
|
@@ -202,12 +243,12 @@ class DuckDBKernel(Kernel):
|
|
|
202
243
|
content = file.read()
|
|
203
244
|
|
|
204
245
|
# You can only execute one statement at a time using SQLite.
|
|
205
|
-
if
|
|
246
|
+
if not self._db.multiple_statements_per_query():
|
|
206
247
|
statements = re.split(r';\r?\n', content)
|
|
207
248
|
for statement in statements:
|
|
208
249
|
self._db.execute(statement)
|
|
209
250
|
|
|
210
|
-
#
|
|
251
|
+
# Other DBMS can execute multiple statements at a time.
|
|
211
252
|
else:
|
|
212
253
|
self._db.execute(content)
|
|
213
254
|
|
|
@@ -35,9 +35,9 @@ class RAElement:
|
|
|
35
35
|
|
|
36
36
|
# if all columns are from the same relation we can skip the relation name
|
|
37
37
|
if len(set(c.table for c in columns)) == 1:
|
|
38
|
-
column_names = ', '.join(f
|
|
38
|
+
column_names = ', '.join(f'{c.current_name} AS "{c.name}"' for c in columns)
|
|
39
39
|
else:
|
|
40
|
-
column_names = ', '.join(f
|
|
40
|
+
column_names = ', '.join(f'{c.current_name} AS "{c.full_name}"' for c in columns)
|
|
41
41
|
|
|
42
42
|
return f'SELECT {column_names} FROM ({sql}) {self._name()}'
|
|
43
43
|
|
|
@@ -334,10 +334,10 @@ class ConditionalSet:
|
|
|
334
334
|
target_table_stmt = table_statements[target_name]
|
|
335
335
|
sql_tables += f' LEFT JOIN {target_table_stmt} ON {join_condition}'
|
|
336
336
|
|
|
337
|
-
sql_join_filters = '1'
|
|
337
|
+
sql_join_filters = '1=1'
|
|
338
338
|
for _, _, _, join_filter in sorted_negative_joins:
|
|
339
339
|
sql_join_filters += f' AND {join_filter}'
|
|
340
340
|
|
|
341
|
-
sql_condition = condition.to_sql(joined_columns) if condition is not None else 1
|
|
341
|
+
sql_condition = condition.to_sql(joined_columns) if condition is not None else '1=1'
|
|
342
342
|
|
|
343
343
|
return f'SELECT DISTINCT {sql_select} FROM {sql_tables} WHERE ({sql_join_filters}) AND ({sql_condition})'
|
|
@@ -11,10 +11,14 @@ src/duckdb_kernel/db/DatabaseError.py
|
|
|
11
11
|
src/duckdb_kernel/db/ForeignKey.py
|
|
12
12
|
src/duckdb_kernel/db/Table.py
|
|
13
13
|
src/duckdb_kernel/db/__init__.py
|
|
14
|
-
src/duckdb_kernel/db/
|
|
15
|
-
src/duckdb_kernel/db/
|
|
16
|
-
src/duckdb_kernel/db/
|
|
17
|
-
src/duckdb_kernel/db/
|
|
14
|
+
src/duckdb_kernel/db/error/EmptyResultError.py
|
|
15
|
+
src/duckdb_kernel/db/error/__init__.py
|
|
16
|
+
src/duckdb_kernel/db/implementation/duckdb/Connection.py
|
|
17
|
+
src/duckdb_kernel/db/implementation/duckdb/__init__.py
|
|
18
|
+
src/duckdb_kernel/db/implementation/postgres/Connection.py
|
|
19
|
+
src/duckdb_kernel/db/implementation/postgres/__init__.py
|
|
20
|
+
src/duckdb_kernel/db/implementation/sqlite/Connection.py
|
|
21
|
+
src/duckdb_kernel/db/implementation/sqlite/__init__.py
|
|
18
22
|
src/duckdb_kernel/magics/MagicCommand.py
|
|
19
23
|
src/duckdb_kernel/magics/MagicCommandCallback.py
|
|
20
24
|
src/duckdb_kernel/magics/MagicCommandException.py
|
|
File without changes
|
|
File without changes
|
{jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/__init__.py
RENAMED
|
File without changes
|
{jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/__main__.py
RENAMED
|
File without changes
|
{jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/db/Column.py
RENAMED
|
File without changes
|
{jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/db/Constraint.py
RENAMED
|
File without changes
|
|
File without changes
|
{jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/db/ForeignKey.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/kernel.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/src/duckdb_kernel/util/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{jupyter-duckdb-0.9.2.2.dev202401171418 → jupyter-duckdb-0.9.2.3}/test/test_result_comparison.py
RENAMED
|
File without changes
|