diff --git a/examples/delete_model.py b/examples/delete_models.py similarity index 92% rename from examples/delete_model.py rename to examples/delete_models.py index c50186c..1d4616d 100644 --- a/examples/delete_model.py +++ b/examples/delete_models.py @@ -15,7 +15,6 @@ """Delete the given Rel model from the given database.""" from argparse import ArgumentParser -import json from urllib.request import HTTPError from railib import api, config, show @@ -23,8 +22,8 @@ def run(database: str, engine: str, model: str, profile: str): cfg = config.read(profile=profile) ctx = api.Context(**cfg) - rsp = api.delete_model(ctx, database, engine, model) - print(json.dumps(rsp, indent=2)) + rsp = api.delete_models(ctx, database, engine, [model]) + print(rsp) if __name__ == "__main__": diff --git a/examples/install_model.py b/examples/install_models.py similarity index 93% rename from examples/install_model.py rename to examples/install_models.py index 8d6937e..c447288 100644 --- a/examples/install_model.py +++ b/examples/install_models.py @@ -15,7 +15,6 @@ """Install the given Rel model in the given database""" from argparse import ArgumentParser -import json from os import path from urllib.request import HTTPError from railib import api, config, show @@ -32,8 +31,8 @@ def run(database: str, engine: str, fname: str, profile: str): models[_sansext(fname)] = fp.read() # model name => model cfg = config.read(profile=profile) ctx = api.Context(**cfg) - rsp = api.install_model(ctx, database, engine, models) - print(json.dumps(rsp, indent=2)) + rsp = api.install_models(ctx, database, engine, models) + print(rsp) if __name__ == "__main__": diff --git a/examples/run-all b/examples/run-all index 07810dd..7fc9a39 100755 --- a/examples/run-all +++ b/examples/run-all @@ -36,7 +36,7 @@ python3 ./show_results.py $DATABASE $ENGINE python3 ./show_problems.py $DATABASE $ENGINE # load model -python3 ./install_model.py $DATABASE $ENGINE hello.rel +python3 ./install_models.py $DATABASE $ENGINE hello.rel python3 ./get_model.py $DATABASE $ENGINE hello python3 ./list_models.py $DATABASE $ENGINE python3 ./delete_model.py $DATABASE $ENGINE hello @@ -60,7 +60,7 @@ python3 ./list_edbs.py $DATABASE $ENGINE python3 ./delete_database.py $DATABASE python3 ./create_database.py $DATABASE python3 ./load_json.py $DATABASE $ENGINE sample.json -r sample_json -python3 ./install_model.py $DATABASE $ENGINE hello.rel +python3 ./install_models.py $DATABASE $ENGINE hello.rel python3 ./clone_database.py $DATABASE_CLONE $DATABASE python3 ./get_database.py $DATABASE_CLONE python3 ./list_databases.py diff --git a/railib/api.py b/railib/api.py index 378b53d..1cf4e8b 100644 --- a/railib/api.py +++ b/railib/api.py @@ -19,9 +19,13 @@ import time import re import io +import sys +import random + from enum import Enum, unique from typing import List, Union from requests_toolbelt import multipart + from . import rest from .pb.message_pb2 import MetadataInfo @@ -114,7 +118,7 @@ class Permission(str, Enum): "create_oauth_client", "delete_database", "delete_engine", - "delete_model", + "delete_models", "disable_user", "enable_user", "delete_oauth_client", @@ -644,18 +648,6 @@ def run(self, ctx: Context, command: str, language: str, inputs: dict = None, ** raise Exception("invalid response type") -def _delete_model_action(name: str) -> dict: - return {"type": "ModifyWorkspaceAction", "delete_source": [name]} - - -def _install_model_action(name: str, model: str) -> dict: - return {"type": "InstallAction", "sources": [_model(name, model)]} - - -def _list_action(): - return {"type": "ListSourceAction"} - - def _list_edb_action(): return {"type": "ListEdbAction"} @@ -703,17 +695,6 @@ def _model(name: str, model: str) -> dict: } -# Returns full list of models. -def _list_models(ctx: Context, database: str, engine: str) -> dict: - tx = Transaction(database, engine, mode=Mode.OPEN) - rsp = tx.run(ctx, _list_action()) - actions = rsp["actions"] - assert len(actions) == 1 - action = actions[0] - models = action["result"]["sources"] - return models - - def create_database(ctx: Context, database: str, source: str = None, **kwargs) -> dict: data = {"name": database, "source_name": source} url = _mkurl(ctx, PATH_DATABASE) @@ -721,25 +702,84 @@ def create_database(ctx: Context, database: str, source: str = None, **kwargs) - return json.loads(rsp.read()) -def delete_model(ctx: Context, database: str, engine: str, model: str) -> dict: - tx = Transaction(database, engine, mode=Mode.OPEN, readonly=False) - actions = [_delete_model_action(model)] - return tx.run(ctx, *actions) +# Returns full list of models. +def list_models(ctx: Context, database: str, engine: str) -> List: + models = [] + out_name = f'model{random.randint(0, sys.maxsize)}' + resp = exec(ctx, database, engine, f'def output:{out_name}[name] = rel:catalog:model(name, _)') + for result in resp.results: + if f'/:output/:{out_name}' in result['relationId']: + table = result['table'].to_pydict() + models.extend([table['v1'][i] for i in range(1, len(table['v1']))]) + + return models + + +def delete_model(ctx: Context, database: str, engine: str, model: str) -> TransactionAsyncResponse: + return delete_models(ctx, database, engine, [model]) + + +def delete_models(ctx: Context, database: str, engine: str, models: List[str]) -> TransactionAsyncResponse: + queries = [ + f'def delete:rel:catalog:model["{model_name}"] = rel:catalog:model["{model_name}"]' + for model_name in models + ] + return exec(ctx, database, engine, '\n'.join(queries), readonly=False) + + +def delete_model_async(ctx: Context, database: str, engine: str, model: str) -> TransactionAsyncResponse: + return delete_models_async(ctx, database, engine, [model]) + + +def delete_models_async(ctx: Context, database: str, engine: str, models: List[str]) -> TransactionAsyncResponse: + queries = [ + f'def delete:rel:catalog:model["{model_name}"] = rel:catalog:model["{model_name}"]' + for model_name in models + ] + return exec_async(ctx, database, engine, '\n'.join(queries), readonly=False) # Returns the named model def get_model(ctx: Context, database: str, engine: str, name: str) -> str: - models = _list_models(ctx, database, engine) - for model in models: - if model["name"] == name: - return model["value"] + out_name = f'model{random.randint(0, sys.maxsize)}' + cmd = f'def output:{out_name} = rel:catalog:model["{name}"]' + resp = exec(ctx, database, engine, cmd) + for result in resp.results: + if f'/:output/:{out_name}' in result['relationId']: + table = result['table'].to_pydict() + return table['v1'][0] raise Exception(f"model '{name}' not found") -def install_model(ctx: Context, database: str, engine: str, models: dict) -> dict: - tx = Transaction(database, engine, mode=Mode.OPEN, readonly=False) - actions = [_install_model_action(name, model) for name, model in models.items()] - return tx.run(ctx, *actions) +def install_models(ctx: Context, database: str, engine: str, models: dict) -> TransactionAsyncResponse: + queries = [] + queries_inputs = {} + randint = random.randint(0, sys.maxsize) + index = 0 + for name, value in models.items(): + input_name = f'input_{randint}_{index}' + queries.append(f'def delete:rel:catalog:model["{name}"] = rel:catalog:model["{name}"]') + queries.append(f'def insert:rel:catalog:model["{name}"] = {input_name}') + + queries_inputs[input_name] = value + index += 1 + + return exec(ctx, database, engine, '\n'.join(queries), inputs=queries_inputs, readonly=False) + + +def install_models_async(ctx: Context, database: str, engine: str, models: dict) -> TransactionAsyncResponse: + queries = [] + queries_inputs = {} + randint = random.randint(0, sys.maxsize) + index = 0 + for name, value in models.items(): + input_name = f'input_{randint}_{index}' + queries.append(f'def delete:rel:catalog:model["{name}"] = rel:catalog:model["{name}"]') + queries.append(f'def insert:rel:catalog:model["{name}"] = {input_name}') + + queries_inputs[input_name] = value + index += 1 + return exec_async(ctx, database, engine, '\n'.join(queries), inputs=queries_inputs, readonly=False) def list_edbs(ctx: Context, database: str, engine: str) -> list: @@ -752,12 +792,6 @@ def list_edbs(ctx: Context, database: str, engine: str) -> list: return rels -# Returns a list of models installed in the given database. -def list_models(ctx: Context, database: str, engine: str) -> list: - models = _list_models(ctx, database, engine) - return [model["name"] for model in models] - - # Generate a rel literal relation for the given dict. def _gen_literal_dict(items: dict) -> str: result = [] diff --git a/test/test_integration.py b/test/test_integration.py index bb45708..9161592 100644 --- a/test/test_integration.py +++ b/test/test_integration.py @@ -68,11 +68,32 @@ def test_v2_exec(self): # results self.assertEqual( { - 'v1': [ - 1, 2, 3, 4, 5], 'v2': [ - 1, 4, 9, 16, 25], 'v3': [ - 1, 8, 27, 64, 125], 'v4': [ - 1, 16, 81, 256, 625]}, rsp.results[0]["table"].to_pydict()) + 'v1': [1, 2, 3, 4, 5], + 'v2': [1, 4, 9, 16, 25], + 'v3': [1, 8, 27, 64, 125], + 'v4': [1, 16, 81, 256, 625] + }, + rsp.results[0]["table"].to_pydict()) + + def test_models(self): + models = api.list_models(ctx, dbname, engine) + self.assertTrue(len(models) > 0) + + models = {'test_model': 'def foo=:bar'} + resp = api.install_models(ctx, dbname, engine, models) + self.assertEqual(resp.transaction['state'], 'COMPLETED') + + value = api.get_model(ctx, dbname, engine, 'test_model') + self.assertEqual(models['test_model'], value) + + models = api.list_models(ctx, dbname, engine) + self.assertTrue('test_model' in models) + + resp = api.delete_models(ctx, dbname, engine, ['test_model']) + self.assertEqual(resp.transaction['state'], 'COMPLETED') + + models = api.list_models(ctx, dbname, engine) + self.assertFalse('test_model' in models) def tearDown(self): api.delete_engine(ctx, engine)