2017-12-15 12:04:17 +00:00
|
|
|
from datasette.app import Datasette
|
2018-03-30 05:10:09 +00:00
|
|
|
import itertools
|
2017-12-15 12:04:17 +00:00
|
|
|
import os
|
2018-04-09 00:06:10 +00:00
|
|
|
import random
|
2017-12-15 12:04:17 +00:00
|
|
|
import sqlite3
|
2018-04-03 13:46:11 +00:00
|
|
|
import sys
|
2018-03-30 05:10:09 +00:00
|
|
|
import string
|
2017-12-15 12:04:17 +00:00
|
|
|
import tempfile
|
|
|
|
import time
|
|
|
|
|
|
|
|
|
2018-05-25 01:12:27 +00:00
|
|
|
def app_client(sql_time_limit_ms=None, max_returned_rows=None, config=None):
|
2017-12-15 12:04:17 +00:00
|
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
|
|
filepath = os.path.join(tmpdir, 'test_tables.db')
|
|
|
|
conn = sqlite3.connect(filepath)
|
|
|
|
conn.executescript(TABLES)
|
|
|
|
os.chdir(os.path.dirname(filepath))
|
2018-04-16 05:22:01 +00:00
|
|
|
plugins_dir = os.path.join(tmpdir, 'plugins')
|
|
|
|
os.mkdir(plugins_dir)
|
|
|
|
open(os.path.join(plugins_dir, 'my_plugin.py'), 'w').write(PLUGIN)
|
2018-05-25 01:12:27 +00:00
|
|
|
config = config or {}
|
|
|
|
config.update({
|
|
|
|
'default_page_size': 50,
|
|
|
|
'max_returned_rows': max_returned_rows or 100,
|
|
|
|
'sql_time_limit_ms': sql_time_limit_ms or 200,
|
|
|
|
})
|
2017-12-15 12:04:17 +00:00
|
|
|
ds = Datasette(
|
|
|
|
[filepath],
|
2018-03-27 16:18:32 +00:00
|
|
|
metadata=METADATA,
|
2018-04-16 05:22:01 +00:00
|
|
|
plugins_dir=plugins_dir,
|
2018-05-25 01:12:27 +00:00
|
|
|
config=config,
|
2017-12-15 12:04:17 +00:00
|
|
|
)
|
|
|
|
ds.sqlite_functions.append(
|
|
|
|
('sleep', 1, lambda n: time.sleep(float(n))),
|
|
|
|
)
|
2018-04-19 05:24:48 +00:00
|
|
|
client = ds.app().test_client
|
|
|
|
client.ds = ds
|
|
|
|
yield client
|
2017-12-15 12:04:17 +00:00
|
|
|
|
|
|
|
|
2018-05-05 22:41:37 +00:00
|
|
|
def app_client_shorter_time_limit():
|
|
|
|
yield from app_client(20)
|
2018-04-18 01:08:51 +00:00
|
|
|
|
|
|
|
|
2018-04-26 04:04:12 +00:00
|
|
|
def app_client_returend_rows_matches_page_size():
|
|
|
|
yield from app_client(max_returned_rows=50)
|
|
|
|
|
|
|
|
|
2018-03-30 06:26:22 +00:00
|
|
|
def generate_compound_rows(num):
|
|
|
|
for a, b, c in itertools.islice(
|
|
|
|
itertools.product(string.ascii_lowercase, repeat=3), num
|
|
|
|
):
|
|
|
|
yield a, b, c, '{}-{}-{}'.format(a, b, c)
|
|
|
|
|
|
|
|
|
2018-04-09 00:06:10 +00:00
|
|
|
def generate_sortable_rows(num):
|
|
|
|
rand = random.Random(42)
|
|
|
|
for a, b in itertools.islice(
|
|
|
|
itertools.product(string.ascii_lowercase, repeat=2), num
|
|
|
|
):
|
|
|
|
yield {
|
|
|
|
'pk1': a,
|
|
|
|
'pk2': b,
|
|
|
|
'content': '{}-{}'.format(a, b),
|
|
|
|
'sortable': rand.randint(-100, 100),
|
|
|
|
'sortable_with_nulls': rand.choice([
|
|
|
|
None, rand.random(), rand.random()
|
|
|
|
]),
|
|
|
|
'sortable_with_nulls_2': rand.choice([
|
|
|
|
None, rand.random(), rand.random()
|
|
|
|
]),
|
2018-04-17 01:41:17 +00:00
|
|
|
'text': rand.choice(['$null', '$blah']),
|
2018-04-09 00:06:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-03-27 16:18:32 +00:00
|
|
|
METADATA = {
|
|
|
|
'title': 'Datasette Title',
|
|
|
|
'description': 'Datasette Description',
|
|
|
|
'license': 'License',
|
|
|
|
'license_url': 'http://www.example.com/license',
|
|
|
|
'source': 'Source',
|
|
|
|
'source_url': 'http://www.example.com/source',
|
|
|
|
'databases': {
|
|
|
|
'test_tables': {
|
|
|
|
'description': 'Test tables description',
|
|
|
|
'tables': {
|
|
|
|
'simple_primary_key': {
|
|
|
|
'description_html': 'Simple <em>primary</em> key',
|
|
|
|
'title': 'This <em>HTML</em> is escaped',
|
2018-04-09 04:58:25 +00:00
|
|
|
},
|
|
|
|
'sortable': {
|
|
|
|
'sortable_columns': [
|
|
|
|
'sortable',
|
|
|
|
'sortable_with_nulls',
|
|
|
|
'sortable_with_nulls_2',
|
2018-04-17 01:41:17 +00:00
|
|
|
'text',
|
2018-04-09 04:58:25 +00:00
|
|
|
]
|
|
|
|
},
|
|
|
|
'no_primary_key': {
|
|
|
|
'sortable_columns': [],
|
2018-04-26 03:42:57 +00:00
|
|
|
'hidden': True,
|
2018-04-09 04:58:25 +00:00
|
|
|
},
|
2018-04-14 14:06:52 +00:00
|
|
|
'units': {
|
|
|
|
'units': {
|
|
|
|
'distance': 'm',
|
|
|
|
'frequency': 'Hz'
|
|
|
|
}
|
|
|
|
},
|
2018-04-22 20:46:18 +00:00
|
|
|
'primary_key_multiple_columns_explicit_label': {
|
2018-04-22 17:51:43 +00:00
|
|
|
'label_column': 'content2',
|
|
|
|
},
|
2018-03-27 16:18:32 +00:00
|
|
|
}
|
2018-04-09 04:58:25 +00:00
|
|
|
},
|
2018-03-27 16:18:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-16 05:22:01 +00:00
|
|
|
PLUGIN = '''
|
|
|
|
from datasette import hookimpl
|
|
|
|
import pint
|
|
|
|
|
|
|
|
ureg = pint.UnitRegistry()
|
|
|
|
|
|
|
|
|
|
|
|
@hookimpl
|
|
|
|
def prepare_connection(conn):
|
|
|
|
def convert_units(amount, from_, to_):
|
|
|
|
"select convert_units(100, 'm', 'ft');"
|
|
|
|
return (amount * ureg(from_)).to(to_).to_tuple()[0]
|
|
|
|
conn.create_function('convert_units', 3, convert_units)
|
2018-04-18 03:12:21 +00:00
|
|
|
|
|
|
|
|
|
|
|
@hookimpl
|
|
|
|
def extra_css_urls():
|
|
|
|
return ['https://example.com/app.css']
|
|
|
|
|
|
|
|
|
|
|
|
@hookimpl
|
|
|
|
def extra_js_urls():
|
|
|
|
return [{
|
|
|
|
'url': 'https://example.com/app.js',
|
|
|
|
'sri': 'SRIHASH',
|
|
|
|
}]
|
2018-04-16 05:22:01 +00:00
|
|
|
'''
|
2018-03-27 16:18:32 +00:00
|
|
|
|
2017-12-15 12:04:17 +00:00
|
|
|
TABLES = '''
|
|
|
|
CREATE TABLE simple_primary_key (
|
2018-04-14 14:55:27 +00:00
|
|
|
id varchar(30) primary key,
|
2017-12-15 12:04:17 +00:00
|
|
|
content text
|
|
|
|
);
|
|
|
|
|
2018-04-14 14:55:27 +00:00
|
|
|
CREATE TABLE primary_key_multiple_columns (
|
|
|
|
id varchar(30) primary key,
|
|
|
|
content text,
|
|
|
|
content2 text
|
|
|
|
);
|
|
|
|
|
2018-04-22 20:46:18 +00:00
|
|
|
CREATE TABLE primary_key_multiple_columns_explicit_label (
|
|
|
|
id varchar(30) primary key,
|
|
|
|
content text,
|
|
|
|
content2 text
|
|
|
|
);
|
|
|
|
|
2017-12-15 12:04:17 +00:00
|
|
|
CREATE TABLE compound_primary_key (
|
|
|
|
pk1 varchar(30),
|
|
|
|
pk2 varchar(30),
|
|
|
|
content text,
|
|
|
|
PRIMARY KEY (pk1, pk2)
|
|
|
|
);
|
|
|
|
|
|
|
|
INSERT INTO compound_primary_key VALUES ('a', 'b', 'c');
|
|
|
|
|
2018-03-30 05:10:09 +00:00
|
|
|
CREATE TABLE compound_three_primary_keys (
|
|
|
|
pk1 varchar(30),
|
|
|
|
pk2 varchar(30),
|
|
|
|
pk3 varchar(30),
|
|
|
|
content text,
|
|
|
|
PRIMARY KEY (pk1, pk2, pk3)
|
|
|
|
);
|
|
|
|
|
2018-04-14 14:55:27 +00:00
|
|
|
CREATE TABLE foreign_key_references (
|
|
|
|
pk varchar(30) primary key,
|
|
|
|
foreign_key_with_label varchar(30),
|
|
|
|
foreign_key_with_no_label varchar(30),
|
|
|
|
FOREIGN KEY (foreign_key_with_label) REFERENCES simple_primary_key(id),
|
|
|
|
FOREIGN KEY (foreign_key_with_no_label) REFERENCES primary_key_multiple_columns(id)
|
|
|
|
);
|
|
|
|
|
2018-04-09 00:06:10 +00:00
|
|
|
CREATE TABLE sortable (
|
|
|
|
pk1 varchar(30),
|
|
|
|
pk2 varchar(30),
|
|
|
|
content text,
|
|
|
|
sortable integer,
|
|
|
|
sortable_with_nulls real,
|
|
|
|
sortable_with_nulls_2 real,
|
2018-04-17 01:41:17 +00:00
|
|
|
text text,
|
2018-04-09 00:06:10 +00:00
|
|
|
PRIMARY KEY (pk1, pk2)
|
|
|
|
);
|
2018-03-30 05:10:09 +00:00
|
|
|
|
2017-12-15 12:04:17 +00:00
|
|
|
CREATE TABLE no_primary_key (
|
|
|
|
content text,
|
|
|
|
a text,
|
|
|
|
b text,
|
|
|
|
c text
|
|
|
|
);
|
|
|
|
|
|
|
|
CREATE TABLE [123_starts_with_digits] (
|
|
|
|
content text
|
|
|
|
);
|
|
|
|
|
|
|
|
CREATE VIEW paginated_view AS
|
|
|
|
SELECT
|
|
|
|
content,
|
|
|
|
'- ' || content || ' -' AS content_extra
|
|
|
|
FROM no_primary_key;
|
|
|
|
|
|
|
|
CREATE TABLE "Table With Space In Name" (
|
|
|
|
pk varchar(30) primary key,
|
|
|
|
content text
|
|
|
|
);
|
|
|
|
|
|
|
|
CREATE TABLE "table/with/slashes.csv" (
|
|
|
|
pk varchar(30) primary key,
|
|
|
|
content text
|
|
|
|
);
|
|
|
|
|
|
|
|
CREATE TABLE "complex_foreign_keys" (
|
|
|
|
pk varchar(30) primary key,
|
|
|
|
f1 text,
|
|
|
|
f2 text,
|
|
|
|
f3 text,
|
|
|
|
FOREIGN KEY ("f1") REFERENCES [simple_primary_key](id),
|
|
|
|
FOREIGN KEY ("f2") REFERENCES [simple_primary_key](id),
|
|
|
|
FOREIGN KEY ("f3") REFERENCES [simple_primary_key](id)
|
|
|
|
);
|
|
|
|
|
2018-04-22 17:51:43 +00:00
|
|
|
CREATE TABLE "custom_foreign_key_label" (
|
|
|
|
pk varchar(30) primary key,
|
|
|
|
foreign_key_with_custom_label text,
|
2018-04-22 20:46:18 +00:00
|
|
|
FOREIGN KEY ("foreign_key_with_custom_label") REFERENCES [primary_key_multiple_columns_explicit_label](id)
|
2018-04-22 17:51:43 +00:00
|
|
|
);
|
|
|
|
|
2018-04-14 14:06:52 +00:00
|
|
|
CREATE TABLE units (
|
|
|
|
pk integer primary key,
|
|
|
|
distance int,
|
|
|
|
frequency int
|
|
|
|
);
|
|
|
|
|
|
|
|
INSERT INTO units VALUES (1, 1, 100);
|
|
|
|
INSERT INTO units VALUES (2, 5000, 2500);
|
|
|
|
INSERT INTO units VALUES (3, 100000, 75000);
|
|
|
|
|
2018-05-05 22:01:14 +00:00
|
|
|
CREATE TABLE searchable (
|
|
|
|
pk integer primary key,
|
|
|
|
text1 text,
|
2018-05-05 22:33:08 +00:00
|
|
|
text2 text,
|
|
|
|
[name with . and spaces] text
|
2018-05-05 22:01:14 +00:00
|
|
|
);
|
|
|
|
|
2018-05-05 22:33:08 +00:00
|
|
|
INSERT INTO searchable VALUES (1, 'barry cat', 'terry dog', 'panther');
|
|
|
|
INSERT INTO searchable VALUES (2, 'terry dog', 'sara weasel', 'puma');
|
2018-05-05 22:01:14 +00:00
|
|
|
|
|
|
|
CREATE VIRTUAL TABLE "searchable_fts"
|
2018-05-05 22:33:08 +00:00
|
|
|
USING FTS3 (text1, text2, [name with . and spaces], content="searchable");
|
|
|
|
INSERT INTO "searchable_fts" (rowid, text1, text2, [name with . and spaces])
|
|
|
|
SELECT rowid, text1, text2, [name with . and spaces] FROM searchable;
|
2018-05-05 22:01:14 +00:00
|
|
|
|
2018-04-03 13:39:50 +00:00
|
|
|
CREATE TABLE [select] (
|
|
|
|
[group] text,
|
|
|
|
[having] text,
|
|
|
|
[and] text
|
|
|
|
);
|
|
|
|
INSERT INTO [select] VALUES ('group', 'having', 'and');
|
|
|
|
|
2018-05-15 15:52:02 +00:00
|
|
|
CREATE TABLE facet_cities (
|
|
|
|
id integer primary key,
|
|
|
|
name text
|
|
|
|
);
|
|
|
|
INSERT INTO facet_cities (id, name) VALUES
|
|
|
|
(1, 'San Francisco'),
|
|
|
|
(2, 'Los Angeles'),
|
|
|
|
(3, 'Detroit'),
|
|
|
|
(4, 'Memnonia')
|
|
|
|
;
|
|
|
|
|
2018-05-12 22:29:06 +00:00
|
|
|
CREATE TABLE facetable (
|
|
|
|
pk integer primary key,
|
2018-05-15 15:52:02 +00:00
|
|
|
planet_int integer,
|
2018-05-12 22:29:06 +00:00
|
|
|
state text,
|
2018-05-15 15:52:02 +00:00
|
|
|
city_id integer,
|
|
|
|
neighborhood text,
|
|
|
|
FOREIGN KEY ("city_id") REFERENCES [facet_cities](id)
|
2018-05-12 22:29:06 +00:00
|
|
|
);
|
2018-05-15 15:52:02 +00:00
|
|
|
INSERT INTO facetable (planet_int, state, city_id, neighborhood) VALUES
|
|
|
|
(1, 'CA', 1, 'Mission'),
|
|
|
|
(1, 'CA', 1, 'Dogpatch'),
|
|
|
|
(1, 'CA', 1, 'SOMA'),
|
|
|
|
(1, 'CA', 1, 'Tenderloin'),
|
|
|
|
(1, 'CA', 1, 'Bernal Heights'),
|
|
|
|
(1, 'CA', 1, 'Hayes Valley'),
|
|
|
|
(1, 'CA', 2, 'Hollywood'),
|
|
|
|
(1, 'CA', 2, 'Downtown'),
|
|
|
|
(1, 'CA', 2, 'Los Feliz'),
|
|
|
|
(1, 'CA', 2, 'Koreatown'),
|
|
|
|
(1, 'MI', 3, 'Downtown'),
|
|
|
|
(1, 'MI', 3, 'Greektown'),
|
|
|
|
(1, 'MI', 3, 'Corktown'),
|
|
|
|
(1, 'MI', 3, 'Mexicantown'),
|
|
|
|
(2, 'MC', 4, 'Arcadia Planitia')
|
2018-05-12 22:29:06 +00:00
|
|
|
;
|
|
|
|
|
2017-12-15 12:04:17 +00:00
|
|
|
INSERT INTO simple_primary_key VALUES (1, 'hello');
|
|
|
|
INSERT INTO simple_primary_key VALUES (2, 'world');
|
|
|
|
INSERT INTO simple_primary_key VALUES (3, '');
|
|
|
|
|
2018-04-14 14:55:27 +00:00
|
|
|
INSERT INTO primary_key_multiple_columns VALUES (1, 'hey', 'world');
|
2018-04-22 20:46:18 +00:00
|
|
|
INSERT INTO primary_key_multiple_columns_explicit_label VALUES (1, 'hey', 'world2');
|
2018-04-14 14:55:27 +00:00
|
|
|
|
|
|
|
INSERT INTO foreign_key_references VALUES (1, 1, 1);
|
|
|
|
|
2017-12-15 12:04:17 +00:00
|
|
|
INSERT INTO complex_foreign_keys VALUES (1, 1, 2, 1);
|
2018-04-22 17:51:43 +00:00
|
|
|
INSERT INTO custom_foreign_key_label VALUES (1, 1);
|
2017-12-15 12:04:17 +00:00
|
|
|
|
|
|
|
INSERT INTO [table/with/slashes.csv] VALUES (3, 'hey');
|
|
|
|
|
|
|
|
CREATE VIEW simple_view AS
|
|
|
|
SELECT content, upper(content) AS upper_content FROM simple_primary_key;
|
|
|
|
|
|
|
|
''' + '\n'.join([
|
|
|
|
'INSERT INTO no_primary_key VALUES ({i}, "a{i}", "b{i}", "c{i}");'.format(i=i + 1)
|
|
|
|
for i in range(201)
|
2018-03-30 05:10:09 +00:00
|
|
|
]) + '\n'.join([
|
2018-03-30 06:26:22 +00:00
|
|
|
'INSERT INTO compound_three_primary_keys VALUES ("{a}", "{b}", "{c}", "{content}");'.format(
|
|
|
|
a=a, b=b, c=c, content=content
|
|
|
|
) for a, b, c, content in generate_compound_rows(1001)
|
2018-04-09 00:06:10 +00:00
|
|
|
]) + '\n'.join([
|
|
|
|
'''INSERT INTO sortable VALUES (
|
|
|
|
"{pk1}", "{pk2}", "{content}", {sortable},
|
2018-04-17 01:41:17 +00:00
|
|
|
{sortable_with_nulls}, {sortable_with_nulls_2}, "{text}");
|
2018-04-09 00:06:10 +00:00
|
|
|
'''.format(
|
|
|
|
**row
|
|
|
|
).replace('None', 'null') for row in generate_sortable_rows(201)
|
2017-12-15 12:04:17 +00:00
|
|
|
])
|
2018-04-03 13:46:11 +00:00
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
filename = sys.argv[-1]
|
|
|
|
if filename.endswith('.db'):
|
|
|
|
conn = sqlite3.connect(filename)
|
|
|
|
conn.executescript(TABLES)
|
|
|
|
print('Test tables written to {}'.format(filename))
|
|
|
|
else:
|
|
|
|
print('Usage: {} name_of_file_to_write.db'.format(sys.argv[0]))
|