Skip to content

Commit 58361e4

Browse files
authored
Merge pull request #1902 from OpenEnergyPlatform/release-v1.0.5-fixed
Release v1.0.5 fixed
2 parents 93ecfa2 + 35a890a commit 58361e4

File tree

74 files changed

+686
-231
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

74 files changed

+686
-231
lines changed

.bumpversion.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[bumpversion]
2-
current_version = 1.0.4
2+
current_version = 1.0.5
33

44
[bumpversion:file:VERSION]
55

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ venv*/
7979
0_env/
8080
/envs
8181
/node_env
82+
/oep-django-5
8283

8384
.DS_Store
8485

@@ -98,3 +99,7 @@ ontologies/
9899
scripts/
99100

100101
site/
102+
103+
# Jenna fuseki graph db installation
104+
/apache-jena-fuseki*/
105+
/apache-jena-fuseki*.tar.gz*

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
exclude: ^.*(.min.js|.min.css|.html|.js|.js.map)$
1+
exclude: ^.*(.min.js|.min.css|.html|.js|.js.map|docs/*|mkdocs.yml)$
22
repos:
33
- repo: https://github.com/pre-commit/pre-commit-hooks
44
rev: v4.4.0

.prettierignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
# Ignore all HTML files, because they contain
22
# django markup that will not be broken by prettier
33
**/*.html
4+
5+
docs/*

CITATION.cff

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ authors:
2828
title: "Open Energy Family - Open Energy Platform (OEP)"
2929
type: software
3030
license: AGPL-3.0-or-later
31-
version: 1.0.4
31+
version: 1.0.5
3232
doi:
33-
date-released: 2024-09-25
33+
date-released: 2024-11-19
3434
url: "https://github.com/OpenEnergyPlatform/oeplatform/"

RELEASE_PROCEDURE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ Before see How to [Contribute](https://github.com/OpenEnergyPlatform/oeplatform/
5252
- `versioneer` automatically updates the version number based on the tag
5353
- this is now the official tagged commit
5454
- Push the tag upstream: `git push upstream --tags`
55-
- Alternatievely: tag on github platform while creating release
55+
- Alternatively: tag on github platform while creating release
5656
1. Make a new release on Github
5757
- https://github.com/OpenEnergyPlatform/oeplatform/releases/new
5858
- make sure that you choose the tag name defined above

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.0.4
1+
1.0.5

api/actions.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -857,11 +857,15 @@ def table_create(schema, table, column_definitions, constraints_definitions):
857857

858858
# check for duplicate column names
859859
if col.name in columns_by_name:
860-
raise APIError("Duplicate column name: %s" % col.name)
860+
error = APIError("Duplicate column name: %s" % col.name)
861+
logger.error(error)
862+
raise error
861863
columns_by_name[col.name] = col
862864
if col.primary_key:
863865
if primary_key_col_names:
864-
raise APIError("Multiple definitions of primary key")
866+
error = APIError("Multiple definitions of primary key")
867+
logger.error(error)
868+
raise error
865869
primary_key_col_names = [col.name]
866870

867871
constraints = []
@@ -887,7 +891,7 @@ def table_create(schema, table, column_definitions, constraints_definitions):
887891
# to column level PK, both must be the same (#1110)
888892
if set(ccolumns) == set(primary_key_col_names):
889893
continue
890-
raise APIError("Multiple definitions of primary key")
894+
raise APIError("Multiple definitions of primary key.")
891895
primary_key_col_names = ccolumns
892896

893897
const = sa.schema.PrimaryKeyConstraint(*ccolumns, **kwargs)
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
from api import actions
2+
from api.actions import has_table
3+
from api.tests import APITestCase
4+
5+
6+
class TestTableNameUnique(APITestCase):
7+
schema_sandbox = "sandbox"
8+
9+
@classmethod
10+
def setUpClass(cls):
11+
super().setUpClass()
12+
actions.perform_sql(f"DROP SCHEMA IF EXISTS {cls.schema_sandbox} CASCADE")
13+
actions.perform_sql(f"CREATE SCHEMA {cls.schema_sandbox}")
14+
actions.perform_sql(f"DROP SCHEMA IF EXISTS _{cls.schema_sandbox} CASCADE")
15+
actions.perform_sql(f"CREATE SCHEMA _{cls.schema_sandbox}")
16+
17+
def test_tables_should_not_exists_on_error(self):
18+
test_duplicate_column_table_name = "table_column_duplicate"
19+
# create table with duplicated column names will should an error
20+
duplicate_field_error_data_struct = {
21+
"columns": [
22+
{"name": "id", "data_type": "bigint"},
23+
{"name": "id", "data_type": "bigint"},
24+
]
25+
}
26+
# create table in default (test) schema (django_db)
27+
self.assertRaises(
28+
AssertionError,
29+
self.create_table,
30+
table=test_duplicate_column_table_name,
31+
structure=duplicate_field_error_data_struct,
32+
schema=self.schema_sandbox,
33+
)
34+
35+
# also check: table should not have been created in oedb
36+
self.assertFalse(
37+
has_table(
38+
{
39+
"table": test_duplicate_column_table_name,
40+
"schema": self.schema_sandbox,
41+
}
42+
)
43+
)

api/views.py

Lines changed: 107 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from django.contrib.auth.mixins import LoginRequiredMixin
1515
from django.contrib.postgres.search import TrigramSimilarity
1616
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
17+
from django.db import DatabaseError, transaction
1718
from django.db.models import Q
1819
from django.db.utils import IntegrityError
1920
from django.http import (
@@ -482,6 +483,100 @@ def validate_column_names(self, column_definitions):
482483
if len(colname) > MAX_COL_NAME_LENGTH:
483484
raise APIError(f"Column name is too long! {err_msg}")
484485

486+
def oep_create_table_transaction(
487+
self,
488+
django_schema_object,
489+
schema,
490+
table,
491+
column_definitions,
492+
constraint_definitions,
493+
):
494+
"""
495+
This method handles atomic table creation transactions on the OEP. It
496+
attempts to create first the django table objects and stored it in
497+
dataedit_tables table. Then it attempts to create the OEDB table.
498+
If there is an error raised during the first two steps the function
499+
will cleanup any table object or table artifacts created during the
500+
process. The order of execution matters, it should always first
501+
create the django table object.
502+
503+
Params:
504+
django_schema_object: The schema object stored in the django
505+
database
506+
schema:
507+
table
508+
column_definitions
509+
constraint_definitions
510+
511+
returns:
512+
table_object: The django table objects that was created
513+
"""
514+
515+
try:
516+
with transaction.atomic():
517+
# First create the table object in the django database.
518+
table_object = self._create_table_object(django_schema_object, table)
519+
# Then attempt to create the OEDB table to check
520+
# if creation will succeed - action includes checks
521+
# and will raise api errors
522+
actions.table_create(
523+
schema, table, column_definitions, constraint_definitions
524+
)
525+
except DatabaseError as e:
526+
# remove any oedb table artifacts left after table creation
527+
# transaction failed
528+
self.__remove_oedb_table_on_exception_raised_during_creation_transaction(
529+
table, schema
530+
)
531+
532+
# also remove any django table object
533+
# find the created django table object
534+
object_to_delete = DBTable.objects.filter(
535+
name=table, schema=django_schema_object
536+
)
537+
# delete it if it exists
538+
if object_to_delete.exists():
539+
object_to_delete.delete()
540+
541+
raise APIError(
542+
message="Error during table creation transaction. All table fragments"
543+
f"have been removed. For further details see: {e}"
544+
)
545+
546+
# for now only return the django table object
547+
# TODO: Check if is necessary to return the response dict returned by the oedb
548+
# table creation function
549+
return table_object
550+
551+
def __remove_oedb_table_on_exception_raised_during_creation_transaction(
552+
self, table, schema
553+
):
554+
"""
555+
This private method handles removing a table form the OEDB only for the case
556+
where an error was raised during table creation. It specifically will delete
557+
the OEDB table created by the user and also the edit_ meta(revision) table
558+
that is automatically created in the background.
559+
"""
560+
# find the created oedb table
561+
if actions.has_table({"table": table, "schema": schema}):
562+
# get table and schema names, also for meta(revision) tables
563+
schema, table = actions.get_table_name(schema, table)
564+
meta_schema = actions.get_meta_schema_name(schema)
565+
566+
# drop the revision table with edit_ prefix
567+
edit_table = actions.get_edit_table_name(schema, table)
568+
actions._get_engine().execute(
569+
'DROP TABLE "{schema}"."{table}" CASCADE;'.format(
570+
schema=meta_schema, table=edit_table
571+
)
572+
)
573+
# drop the data table
574+
actions._get_engine().execute(
575+
'DROP TABLE "{schema}"."{table}" CASCADE;'.format(
576+
schema=schema, table=table
577+
)
578+
)
579+
485580
@load_cursor()
486581
def __create_table(
487582
self,
@@ -510,11 +605,13 @@ def __create_table(
510605
raise embargo_error
511606

512607
if embargo_payload_check:
513-
table_object = self._create_table_object(schema_object, table)
514-
actions.table_create(
515-
schema, table, column_definitions, constraint_definitions
608+
table_object = self.oep_create_table_transaction(
609+
django_schema_object=schema_object,
610+
table=table,
611+
schema=schema,
612+
column_definitions=column_definitions,
613+
constraint_definitions=constraint_definitions,
516614
)
517-
518615
self._apply_embargo(table_object, embargo_data)
519616

520617
if metadata:
@@ -534,9 +631,12 @@ def __create_table(
534631
)
535632

536633
else:
537-
table_object = self._create_table_object(schema_object, table)
538-
actions.table_create(
539-
schema, table, column_definitions, constraint_definitions
634+
table_object = self.oep_create_table_transaction(
635+
django_schema_object=schema_object,
636+
table=table,
637+
schema=schema,
638+
column_definitions=column_definitions,
639+
constraint_definitions=constraint_definitions,
540640
)
541641
self._assign_table_holder(request.user, schema, table)
542642

0 commit comments

Comments
 (0)