Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on:
workflow_dispatch:
inputs:
super_ref:
description: 'brimdata/super branch, tag, or SHA to test against'
description: 'superdb/super branch, tag, or SHA to test against'
default: main
required: false
repository_dispatch:
Expand All @@ -22,7 +22,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/checkout@v4
with:
repository: brimdata/super
repository: superdb/super
ref: ${{ inputs.super_ref || 'main' }}
path: super
- uses: actions/setup-go@v5
Expand All @@ -33,7 +33,7 @@ jobs:
working-directory: super
- name: Start super db serve (no auth)
run: |
$GITHUB_WORKSPACE/bin/super db -db $(mktemp -d) serve -l localhost:9867 &
$GITHUB_WORKSPACE/bin/super db serve -l localhost:9867 $(mktemp -d) &
for i in $(seq 1 30); do
curl -sf http://localhost:9867/status 2>/dev/null && break
sleep 1
Expand All @@ -51,7 +51,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/checkout@v4
with:
repository: brimdata/super
repository: superdb/super
ref: ${{ inputs.super_ref || 'main' }}
path: super
- uses: actions/setup-go@v5
Expand All @@ -65,12 +65,13 @@ jobs:
working-directory: super
- name: Start super db serve (with auth)
run: |
$GITHUB_WORKSPACE/bin/super db -db $(mktemp -d) serve -l localhost:9867 \
$GITHUB_WORKSPACE/bin/super db serve -l localhost:9867 \
-auth.enabled=t \
-auth.audience=a \
-auth.clientid=c \
-auth.domain=d \
-auth.jwkspath=$GITHUB_WORKSPACE/super/service/testdata/auth-public-jwks.json &
-auth.jwkspath=$GITHUB_WORKSPACE/super/service/testdata/auth-public-jwks.json \
$(mktemp -d) &
for i in $(seq 1 30); do
curl -sf http://localhost:9867/status 2>/dev/null && break
sleep 1
Expand All @@ -81,7 +82,7 @@ jobs:
-audience a -domain d -keyid testkey \
-privatekeyfile $GITHUB_WORKSPACE/super/service/testdata/auth-private-key \
-tenantid t -userid u)
$GITHUB_WORKSPACE/bin/super db auth store -access "$token" -db http://localhost:9867
$GITHUB_WORKSPACE/bin/super db attach -q -token "$token" http://localhost:9867
- uses: actions/setup-python@v5
with:
python-version: '3.x'
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ dependencies, and start a local SuperDB service:
python3 -m venv .venv
source .venv/bin/activate
pip3 install -e '.[test]'
super db -db $(mktemp -d) serve
super db serve $(mktemp -d)
```

Then in another shell (with the virtual environment activated):
Expand Down
20 changes: 10 additions & 10 deletions superdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,31 +32,31 @@ def __get_auth_token(self, config_dir):
return creds['services'][self.base_url]['access']
return None

def create_pool(self, name, layout={'order': 'desc', 'keys': [['ts']]},
thresh=0):
r = self.session.post(self.base_url + '/pool', json={
def create_table(self, name, layout={'order': 'desc', 'keys': [['ts']]},
thresh=0):
r = self.session.post(self.base_url + '/table', json={
'name': name,
'layout': layout,
'thresh': thresh,
})
self.__raise_for_status(r)

def load(self, pool_name_or_id, data, branch_name='main',
def load(self, table_name_or_id, data, branch_name='main',
commit_author=getpass.getuser(), commit_body='',
mime_type=None):
pool = urllib.parse.quote(pool_name_or_id, safe='')
table = urllib.parse.quote(table_name_or_id, safe='')
branch = urllib.parse.quote(branch_name, safe='')
url = self.base_url + '/pool/' + pool + '/branch/' + branch
url = self.base_url + '/table/' + table + '/branch/' + branch
commit_message = {'author': commit_author, 'body': commit_body}
headers = {'SuperDB-Commit': json.dumps(commit_message)}
if mime_type is not None:
headers['Content-Type'] = mime_type
r = self.session.post(url, headers=headers, data=data)
self.__raise_for_status(r)

def delete_pool(self, pool_name_or_id):
pool = urllib.parse.quote(pool_name_or_id, safe='')
r = self.session.delete(self.base_url + '/pool/' + pool)
def delete_table(self, table_name_or_id):
table = urllib.parse.quote(table_name_or_id, safe='')
r = self.session.delete(self.base_url + '/table/' + table)
self.__raise_for_status(r)

def query(self, query, safe=True):
Expand Down Expand Up @@ -96,7 +96,7 @@ def query(self, query, safe=True):
try:
reader = pa.ipc.open_stream(r.raw)
except pa.lib.ArrowInvalid as e:
# An empty response body (no schema) means either the pool has no
# An empty response body (no schema) means either the table has no
# data or the data contains a type the Arrow encoder can't handle
# (e.g. an empty record). Both cases are indistinguishable at the
# HTTP level when streaming, so both are silently treated as an
Expand Down
6 changes: 3 additions & 3 deletions test_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,16 @@
def test_authenticated_client_can_query():
client = Client()
name = 'test_auth_' + uuid.uuid4().hex[:8]
client.create_pool(name)
client.create_table(name)
try:
client.load(name, b'{a: 1}', mime_type='application/x-sup')
assert list(client.query(f'from {name}')) == [{'a': 1}]
finally:
client.delete_pool(name)
client.delete_table(name)


def test_unauthenticated_client_raises_request_error():
# A client with no credentials (config_dir='') should be rejected by an
# auth-enabled service on any request.
with pytest.raises(RequestError):
Client(config_dir='').create_pool('x')
Client(config_dir='').create_table('x')
44 changes: 22 additions & 22 deletions test_superdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
the service is not reachable.

Start a local instance with:
super db serve
super db serve $(mktemp -d)
"""

import datetime
Expand Down Expand Up @@ -75,17 +75,17 @@ def client():


@pytest.fixture(scope='session')
def pool(client):
def table(client):
name = 'test_types_' + uuid.uuid4().hex[:8]
client.create_pool(name)
client.create_table(name)
client.load(name, _SUP_ALL_TYPES.encode(), mime_type='application/x-sup')
yield name
client.delete_pool(name)
client.delete_table(name)


@pytest.fixture(scope='session')
def record(client, pool):
results = list(client.query(f'from {pool}'))
def record(client, table):
results = list(client.query(f'from {table}'))
assert len(results) == 1
return results[0]

Expand Down Expand Up @@ -235,82 +235,82 @@ def test_multiple_record_types_raises_mixed_types_error(client):
# With safe=True (default), query() runs a pre-flight check and raises
# MixedTypesError before requesting Arrow data, preventing silent truncation.
name = 'test_multi_' + uuid.uuid4().hex[:8]
client.create_pool(name)
client.create_table(name)
try:
client.load(name, b'{a: 1}\n{b: "hello"}',
mime_type='application/x-sup')
with pytest.raises(MixedTypesError) as exc_info:
list(client.query(f'from {name}'))
assert exc_info.value.type_count == 2
finally:
client.delete_pool(name)
client.delete_table(name)


def test_multiple_record_types_safe_false_returns_partial(client):
# With safe=False the pre-flight check is skipped. The Arrow encoder
# silently truncates at the first schema change, so callers that opt out
# must know their data is homogeneous.
name = 'test_multi_unsafe_' + uuid.uuid4().hex[:8]
client.create_pool(name)
client.create_table(name)
try:
client.load(name, b'{a: 1}\n{b: "hello"}',
mime_type='application/x-sup')
results = list(client.query(f'from {name}', safe=False))
assert len(results) < 2
finally:
client.delete_pool(name)
client.delete_table(name)


def test_non_record_values_raises_non_record_error(client):
# Data loaded in line format is stored as top-level strings, not records.
# With safe=True (default), query() detects this and raises NonRecordError.
name = 'test_nonrec_' + uuid.uuid4().hex[:8]
client.create_pool(name)
client.create_table(name)
try:
client.load(name, b'hello\nworld', mime_type='application/x-line')
with pytest.raises(NonRecordError) as exc_info:
list(client.query(f'from {name}'))
assert exc_info.value.kinds == ['primitive']
finally:
client.delete_pool(name)
client.delete_table(name)


def test_non_record_values_safe_false_returns_empty(client):
# With safe=False, non-record values are not detected. Arrow cannot encode
# non-record top-level values, so the server returns an empty stream.
name = 'test_nonrec_unsafe_' + uuid.uuid4().hex[:8]
client.create_pool(name)
client.create_table(name)
try:
client.load(name, b'hello\nworld', mime_type='application/x-line')
assert list(client.query(f'from {name}', safe=False)) == []
finally:
client.delete_pool(name)
client.delete_table(name)


def test_empty_record_returns_no_results(client):
# Arrow does not support records with no fields. The server returns an
# empty stream rather than an error, so the client silently returns no
# results. See the README for details.
name = 'test_empty_' + uuid.uuid4().hex[:8]
client.create_pool(name)
client.create_table(name)
try:
client.load(name, b'{}', mime_type='application/x-sup')
assert list(client.query(f'from {name}')) == []
finally:
client.delete_pool(name)
client.delete_table(name)


def test_duplicate_pool_raises_request_error(client):
def test_duplicate_table_raises_request_error(client):
name = 'test_dup_' + uuid.uuid4().hex[:8]
client.create_pool(name)
client.create_table(name)
try:
with pytest.raises(RequestError):
client.create_pool(name)
client.create_table(name)
finally:
client.delete_pool(name)
client.delete_table(name)


def test_query_nonexistent_pool_raises_request_error(client):
name = 'test_nopool_' + uuid.uuid4().hex[:8]
def test_query_nonexistent_table_raises_request_error(client):
name = 'test_notable_' + uuid.uuid4().hex[:8]
with pytest.raises(RequestError):
list(client.query(f'from {name}'))
Loading