diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/continuous_integration.yml index 64537d94..ea893275 100644 --- a/.github/workflows/continuous_integration.yml +++ b/.github/workflows/continuous_integration.yml @@ -14,9 +14,6 @@ jobs: validate-models: name: CI runs-on: ubuntu-latest - defaults: - run: - working-directory: dev/ steps: - uses: actions/checkout@v6 @@ -27,7 +24,7 @@ jobs: python-version: ${{ env.PYTHON_VERSION }} - name: Install requirements - run: pip install -r requirements.txt + run: pip install -r dev/requirements.txt - name: Check black if: always() @@ -54,11 +51,11 @@ jobs: run: make validate-models - name: Generate models.yml from collections - run: python src/join_models_yml.py + run: python dev/src/join_models_yml.py - name: Check if models.yml is up-to-date run: | - if git diff --exit-code ../models.yml; then + if git diff --exit-code ./models.yml; then echo "✅ models.yml is up-to-date" else echo "❌ models.yml is out of sync with collections/" diff --git a/.github/workflows/test-sql.yml b/.github/workflows/test-sql.yml new file mode 100644 index 00000000..608a50aa --- /dev/null +++ b/.github/workflows/test-sql.yml @@ -0,0 +1,59 @@ +name: Test SQL +on: [pull_request] +jobs: + test: + runs-on: ubuntu-latest + # TODO: What container should be used? This is from the github example + container: node:20-bookworm-slim + + services: + postgres: + image: postgres:15 + env: + POSTGRES_PASSWORD: password + POSTGRES_USER: openslides + POSTGRES_DB: openslides + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + steps: + - name: Check out code + uses: actions/checkout@v4 + + - name: Install psql + run: | + apt-get update + apt-get install --yes --no-install-recommends postgresql-client + + - name: apply schema + shell: bash + run: bash dev/scripts/apply_db_schema.sh + env: + DATABASE_HOST: postgres + DATABASE_PORT: 5432 + DATABASE_USER: openslides + DATABASE_NAME: openslides + PGPASSWORD: password + + - name: insert base data + shell: bash + run: bash dev/scripts/apply_data.sh base_data.sql + env: + DATABASE_HOST: postgres + DATABASE_PORT: 5432 + DATABASE_USER: openslides + DATABASE_NAME: openslides + PGPASSWORD: password + + - name: insert test data + shell: bash + run: bash dev/scripts/apply_data.sh test_data.sql + env: + DATABASE_HOST: postgres + DATABASE_PORT: 5432 + DATABASE_USER: openslides + DATABASE_NAME: openslides + PGPASSWORD: password diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..5e504384 --- /dev/null +++ b/.gitignore @@ -0,0 +1,34 @@ +# Patterns of intentionally untracked files that Git should ignore + +# Private development data +private/ + +# Python compiled files +__pycache__/ + +# Python virtual environment target directory +.virtualenv/ +.venv/ + +# Mypy (static type checker) cache +.mypy_cache/ + +# Pytest, pytest-cov and Coverage.py cache +.pytest_cache/ +.coverage +htmlcov/ + +# Specific editor files +.vscode/* +.vimrc + +# coverage + +migrations/*.json +migrations/*.sql + +# secrets +secrets/ + +# code-workspaces +*.code-workspace diff --git a/.sqlfluffignore b/.sqlfluffignore new file mode 100644 index 00000000..01bb95cc --- /dev/null +++ b/.sqlfluffignore @@ -0,0 +1 @@ +schema_relational.sql diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..f4ade3b1 --- /dev/null +++ b/Makefile @@ -0,0 +1,69 @@ +# Commands inside the container +paths = dev/src/ dev/tests/ + +all: pyupgrade black autoflake isort flake8 mypy sqlfluff + +pyupgrade: + pyupgrade --py310-plus --exit-zero-even-if-changed $$(find . -name '*.py') + +check-pyupgrade: + pyupgrade --py310-plus $$(find . -name '*.py') + +black: + black $(paths) + +check-black: + black --check --diff $(paths) + +autoflake: + autoflake $(paths) + +isort: + isort $(paths) + +check-isort: + isort --check-only --diff $(paths) + +flake8: + flake8 $(paths) + +mypy: + mypy dev/src/ + +sqlfluff: + sqlfluff fix --dialect postgres --verbose + +validate-models: + python -m dev.src.validate + +generate-relational-schema: + python -m dev.src.generate_sql_schema + +drop-database: + dropdb -f -e --if-exists -h ${DATABASE_HOST} -p ${DATABASE_PORT} -U ${DATABASE_USER} ${DATABASE_NAME} + +create-database: + createdb -e -h ${DATABASE_HOST} -p ${DATABASE_PORT} -U ${DATABASE_USER} ${DATABASE_NAME} + +apply-db-schema: + dev/scripts/apply_db_schema.sh + +apply-test-data: + dev/scripts/apply_data.sh base_data.sql + dev/scripts/apply_data.sh test_data.sql + +create-database-with-schema: drop-database create-database apply-db-schema + +create-test-data: create-database-with-schema apply-test-data + +run-psql: + psql -h ${DATABASE_HOST} -p ${DATABASE_PORT} -U ${DATABASE_USER} -d ${DATABASE_NAME} + +# Docker manage commands + +run-dev: + USER_ID=$$(id -u $${USER}) GROUP_ID=$$(id -g $${USER}) docker compose -f dev/docker-compose.yml up -d --build + docker compose -f dev/docker-compose.yml exec models bash --rcfile /etc/bash_completion + +stop-dev: + docker compose -f dev/docker-compose.yml down diff --git a/collections/agenda_item.yml b/collections/agenda_item.yml index 10bbb7bd..1092a3b6 100644 --- a/collections/agenda_item.yml +++ b/collections/agenda_item.yml @@ -48,6 +48,11 @@ weight: content_object_id: type: generic-relation + reference: + - motion + - motion_block + - assignment + - topic to: collections: - motion @@ -61,6 +66,7 @@ content_object_id: constant: true parent_id: type: relation + reference: agenda_item to: agenda_item/child_ids equal_fields: meeting_id restriction_mode: A @@ -82,6 +88,7 @@ projection_ids: restriction_mode: A meeting_id: type: relation + reference: meeting to: meeting/agenda_item_ids required: true restriction_mode: A diff --git a/collections/assignment.yml b/collections/assignment.yml index b0b62c23..d72be76e 100644 --- a/collections/assignment.yml +++ b/collections/assignment.yml @@ -32,6 +32,7 @@ number_poll_candidates: restriction_mode: A sequential_number: type: number + sequence_scope: meeting_id description: The (positive) serial number of this model in its meeting. This number is auto-generated and read-only. read_only: true required: true @@ -82,6 +83,7 @@ projection_ids: restriction_mode: A meeting_id: type: relation + reference: meeting to: meeting/assignment_ids required: true restriction_mode: A diff --git a/collections/assignment_candidate.yml b/collections/assignment_candidate.yml index dbb091b4..7e4485c9 100644 --- a/collections/assignment_candidate.yml +++ b/collections/assignment_candidate.yml @@ -11,6 +11,7 @@ weight: assignment_id: type: relation + reference: assignment to: assignment/candidate_ids equal_fields: meeting_id restriction_mode: A @@ -18,11 +19,13 @@ assignment_id: constant: true meeting_user_id: type: relation + reference: meeting_user to: meeting_user/assignment_candidate_ids restriction_mode: A constant: true meeting_id: type: relation + reference: meeting to: meeting/assignment_candidate_ids required: true restriction_mode: A diff --git a/collections/chat_group.yml b/collections/chat_group.yml index 90d41d50..53acc76f 100644 --- a/collections/chat_group.yml +++ b/collections/chat_group.yml @@ -12,7 +12,6 @@ weight: type: number default: 10000 restriction_mode: A - chat_message_ids: type: relation-list to: chat_message/chat_group_id @@ -31,6 +30,7 @@ write_group_ids: restriction_mode: A meeting_id: type: relation + reference: meeting to: meeting/chat_group_ids required: true restriction_mode: A diff --git a/collections/chat_message.yml b/collections/chat_message.yml index 8f2db8d8..b4290a1b 100644 --- a/collections/chat_message.yml +++ b/collections/chat_message.yml @@ -14,17 +14,20 @@ created: restriction_mode: A meeting_user_id: type: relation + reference: meeting_user to: meeting_user/chat_message_ids restriction_mode: A constant: true chat_group_id: type: relation + reference: chat_group to: chat_group/chat_message_ids restriction_mode: A required: true constant: true meeting_id: type: relation + reference: meeting to: meeting/chat_message_ids required: true restriction_mode: A diff --git a/collections/committee.yml b/collections/committee.yml index efb61f53..9055bafe 100644 --- a/collections/committee.yml +++ b/collections/committee.yml @@ -23,13 +23,37 @@ meeting_ids: default_meeting_id: type: relation to: meeting/default_meeting_for_committee_id + reference: meeting restriction_mode: A user_ids: type: relation-list to: user/committee_ids restriction_mode: A read_only: true - description: "Calculated field." + description: "Calculated field: All users which are in a group of a meeting, belonging to the committee or beeing manager of the committee" + sql: | + ( + SELECT array_agg(DISTINCT user_id ORDER BY user_id) + FROM ( + -- Select user_ids from committees meetings + SELECT mu.user_id + FROM meeting_t AS m + INNER JOIN meeting_user_t AS mu ON mu.meeting_id = m.id + WHERE m.committee_id = c.id + + UNION + + -- Select user_ids from committee managers + SELECT cmu.user_id + FROM nm_committee_manager_ids_user_t cmu + WHERE cmu.committee_id = c.id + + UNION + + -- Select user_id from home committees + SELECT u.id FROM user_t u WHERE u.home_committee_id = c.id + ) _ + ) AS user_ids manager_ids: type: relation-list to: user/committee_management_ids @@ -39,6 +63,7 @@ parent_id: type: relation to: committee/child_ids restriction_mode: A + reference: committee child_ids: type: relation-list to: committee/parent_id @@ -69,6 +94,7 @@ organization_tag_ids: restriction_mode: A organization_id: type: relation + reference: organization to: organization/committee_ids required: true restriction_mode: A diff --git a/collections/gender.yml b/collections/gender.yml index 3cfe5a3e..6758ee29 100644 --- a/collections/gender.yml +++ b/collections/gender.yml @@ -14,8 +14,10 @@ organization_id: required: true to: organization/gender_ids restriction_mode: A + reference: organization user_ids: type: relation-list to: user/gender_id restriction_mode: A + reference: user diff --git a/collections/group.yml b/collections/group.yml index 8b704a2d..1174c885 100644 --- a/collections/group.yml +++ b/collections/group.yml @@ -122,22 +122,27 @@ poll_ids: restriction_mode: A used_as_motion_poll_default_id: type: relation + reference: meeting to: meeting/motion_poll_default_group_ids restriction_mode: A used_as_assignment_poll_default_id: type: relation + reference: meeting to: meeting/assignment_poll_default_group_ids restriction_mode: A used_as_topic_poll_default_id: type: relation + reference: meeting to: meeting/topic_poll_default_group_ids restriction_mode: A used_as_poll_default_id: type: relation + reference: meeting to: meeting/poll_default_group_ids restriction_mode: A meeting_id: type: relation + reference: meeting to: meeting/group_ids required: true restriction_mode: A diff --git a/collections/history_entry.yml b/collections/history_entry.yml index 266d9c22..937b4ac7 100644 --- a/collections/history_entry.yml +++ b/collections/history_entry.yml @@ -20,13 +20,19 @@ model_id: - assignment field: history_entry_ids restriction_mode: A + reference: + - user + - motion + - assignment position_id: type: relation to: history_position/entry_ids required: true restriction_mode: A constant: true + reference: history_position meeting_id: type: relation to: meeting/relevant_history_entry_ids restriction_mode: A + reference: meeting diff --git a/collections/history_position.yml b/collections/history_position.yml index cac6605b..ff4134f9 100644 --- a/collections/history_position.yml +++ b/collections/history_position.yml @@ -16,6 +16,7 @@ user_id: type: relation to: user/history_position_ids restriction_mode: A + reference: user entry_ids: type: relation-list to: history_entry/position_id diff --git a/collections/list_of_speakers.yml b/collections/list_of_speakers.yml index e70d254e..e622a996 100644 --- a/collections/list_of_speakers.yml +++ b/collections/list_of_speakers.yml @@ -10,6 +10,7 @@ closed: restriction_mode: A sequential_number: type: number + sequence_scope: meeting_id description: The (positive) serial number of this model in its meeting. This number is auto-generated and read-only. read_only: true required: true @@ -21,6 +22,12 @@ moderator_notes: content_object_id: type: generic-relation + reference: + - motion + - motion_block + - assignment + - topic + - meeting_mediafile to: collections: - motion @@ -53,6 +60,7 @@ projection_ids: restriction_mode: A meeting_id: type: relation + reference: meeting to: meeting/list_of_speakers_ids required: true restriction_mode: A diff --git a/collections/mediafile.yml b/collections/mediafile.yml index c0f47784..2ba010de 100644 --- a/collections/mediafile.yml +++ b/collections/mediafile.yml @@ -35,9 +35,11 @@ token: published_to_meetings_in_organization_id: type: relation to: organization/published_mediafile_ids + reference: organization restriction_mode: A parent_id: type: relation + reference: mediafile to: mediafile/child_ids equal_fields: owner_id restriction_mode: A @@ -51,6 +53,9 @@ owner_id: to: - meeting/mediafile_ids - organization/mediafile_ids + reference: + - meeting + - organization restriction_mode: A required: true constant: true diff --git a/collections/meeting.yml b/collections/meeting.yml index 9aaa46ec..0cf84141 100644 --- a/collections/meeting.yml +++ b/collections/meeting.yml @@ -20,17 +20,19 @@ welcome_text: # General name: type: string - maxLength: 100 + maxLength: 200 default: OpenSlides restriction_mode: A required: true is_active_in_organization_id: type: relation + reference: organization to: organization/active_meeting_ids restriction_mode: F description: Backrelation and boolean flag at once is_archived_in_organization_id: type: relation + reference: organization to: organization/archived_meeting_ids restriction_mode: F description: Backrelation and boolean flag at once @@ -81,6 +83,7 @@ jitsi_room_password: # System template_for_organization_id: type: relation + reference: organization to: organization/template_meeting_ids restriction_mode: B enable_anonymous: @@ -363,11 +366,13 @@ list_of_speakers_intervention_time: # Motions motions_default_workflow_id: type: relation + reference: motion_workflow to: motion_workflow/default_workflow_meeting_id required: true restriction_mode: B motions_default_amendment_workflow_id: type: relation + reference: motion_workflow to: motion_workflow/default_amendment_workflow_meeting_id required: true restriction_mode: B @@ -595,10 +600,12 @@ motion_poll_projection_max_columns: poll_candidate_list_ids: type: relation-list to: poll_candidate_list/meeting_id + reference: poll_candidate_list restriction_mode: B poll_candidate_ids: type: relation-list to: poll_candidate/meeting_id + reference: poll_candidate restriction_mode: B # Users @@ -929,16 +936,19 @@ motion_state_ids: poll_ids: type: relation-list to: poll/meeting_id + reference: poll on_delete: CASCADE restriction_mode: B option_ids: type: relation-list to: option/meeting_id + reference: option on_delete: CASCADE restriction_mode: B vote_ids: type: relation-list to: vote/meeting_id + reference: vote on_delete: CASCADE restriction_mode: B assignment_ids: @@ -976,70 +986,87 @@ structure_level_ids: logo_projector_main_id: type: relation to: meeting_mediafile/used_as_logo_projector_main_in_meeting_id + reference: meeting_mediafile restriction_mode: B logo_projector_header_id: type: relation to: meeting_mediafile/used_as_logo_projector_header_in_meeting_id + reference: meeting_mediafile restriction_mode: B logo_web_header_id: type: relation to: meeting_mediafile/used_as_logo_web_header_in_meeting_id + reference: meeting_mediafile restriction_mode: B logo_pdf_header_l_id: type: relation to: meeting_mediafile/used_as_logo_pdf_header_l_in_meeting_id + reference: meeting_mediafile restriction_mode: B logo_pdf_header_r_id: type: relation to: meeting_mediafile/used_as_logo_pdf_header_r_in_meeting_id + reference: meeting_mediafile restriction_mode: B logo_pdf_footer_l_id: type: relation to: meeting_mediafile/used_as_logo_pdf_footer_l_in_meeting_id + reference: meeting_mediafile restriction_mode: B logo_pdf_footer_r_id: type: relation to: meeting_mediafile/used_as_logo_pdf_footer_r_in_meeting_id + reference: meeting_mediafile restriction_mode: B logo_pdf_ballot_paper_id: type: relation to: meeting_mediafile/used_as_logo_pdf_ballot_paper_in_meeting_id + reference: meeting_mediafile restriction_mode: B font_regular_id: type: relation to: meeting_mediafile/used_as_font_regular_in_meeting_id + reference: meeting_mediafile restriction_mode: B font_italic_id: type: relation to: meeting_mediafile/used_as_font_italic_in_meeting_id + reference: meeting_mediafile restriction_mode: B font_bold_id: type: relation to: meeting_mediafile/used_as_font_bold_in_meeting_id + reference: meeting_mediafile restriction_mode: B font_bold_italic_id: type: relation to: meeting_mediafile/used_as_font_bold_italic_in_meeting_id + reference: meeting_mediafile restriction_mode: B font_monospace_id: type: relation to: meeting_mediafile/used_as_font_monospace_in_meeting_id + reference: meeting_mediafile restriction_mode: B font_chyron_speaker_name_id: type: relation to: meeting_mediafile/used_as_font_chyron_speaker_name_in_meeting_id + reference: meeting_mediafile restriction_mode: B font_projector_h1_id: type: relation to: meeting_mediafile/used_as_font_projector_h1_in_meeting_id + reference: meeting_mediafile restriction_mode: B font_projector_h2_id: type: relation to: meeting_mediafile/used_as_font_projector_h2_in_meeting_id + reference: meeting_mediafile restriction_mode: B # Other relations committee_id: type: relation + reference: committee to: committee/meeting_ids required: true restriction_mode: A @@ -1057,22 +1084,32 @@ present_user_ids: to: user/is_present_in_meeting_ids restriction_mode: B user_ids: - type: number[] + type: relation-list description: Calculated. All user ids from all users assigned to groups of this meeting. read_only: true + sql: | + ( + SELECT array_agg(DISTINCT mu.user_id ORDER BY mu.user_id) + FROM meeting_user_t mu + WHERE mu.meeting_id = m.id + ) AS user_ids + to: user/meeting_ids restriction_mode: A reference_projector_id: type: relation + reference: projector to: projector/used_as_reference_projector_meeting_id required: true restriction_mode: B list_of_speakers_countdown_id: type: relation to: projector_countdown/used_as_list_of_speakers_countdown_meeting_id + reference: projector_countdown restriction_mode: B poll_countdown_id: type: relation to: projector_countdown/used_as_poll_countdown_meeting_id + reference: projector_countdown restriction_mode: B projection_ids: type: relation-list @@ -1151,16 +1188,19 @@ default_projector_poll_ids: required: true default_group_id: type: relation + reference: group to: group/default_group_for_meeting_id required: true restriction_mode: B admin_group_id: type: relation to: group/admin_group_for_meeting_id + reference: group restriction_mode: A anonymous_group_id: type: relation to: group/anonymous_group_for_meeting_id + reference: group restriction_mode: B relevant_history_entry_ids: type: relation-list diff --git a/collections/meeting_mediafile.yml b/collections/meeting_mediafile.yml index af915cbf..e41d6f4d 100644 --- a/collections/meeting_mediafile.yml +++ b/collections/meeting_mediafile.yml @@ -7,11 +7,13 @@ id: mediafile_id: type: relation to: mediafile/meeting_mediafile_ids + reference: mediafile required: true restriction_mode: A meeting_id: type: relation to: meeting/meeting_mediafile_ids + reference: meeting required: true restriction_mode: A is_public: diff --git a/collections/meeting_user.yml b/collections/meeting_user.yml index 1f449e2d..841ea7b3 100644 --- a/collections/meeting_user.yml +++ b/collections/meeting_user.yml @@ -24,12 +24,14 @@ locked_out: user_id: type: relation + reference: user to: user/meeting_user_ids required: true restriction_mode: C constant: true meeting_id: type: relation + reference: meeting to: meeting/meeting_user_ids required: true restriction_mode: C @@ -73,6 +75,7 @@ assignment_candidate_ids: restriction_mode: A vote_delegated_to_id: type: relation + reference: meeting_user to: meeting_user/vote_delegations_from_ids equal_fields: meeting_id restriction_mode: A @@ -89,8 +92,8 @@ chat_message_ids: group_ids: type: relation-list to: group/meeting_user_ids - equal_fields: meeting_id required: true + equal_fields: meeting_id restriction_mode: A structure_level_ids: type: relation-list diff --git a/collections/motion.yml b/collections/motion.yml index 0083d0e1..aef3dcf6 100644 --- a/collections/motion.yml +++ b/collections/motion.yml @@ -14,6 +14,7 @@ number_value: restriction_mode: D sequential_number: type: number + sequence_scope: meeting_id description: The (positive) serial number of this model in its meeting. This number is auto-generated and read-only. read_only: true required: true @@ -81,6 +82,7 @@ marked_forwarded: lead_motion_id: type: relation + reference: motion to: motion/amendment_ids equal_fields: meeting_id restriction_mode: C @@ -92,6 +94,7 @@ amendment_ids: restriction_mode: C sort_parent_id: type: relation + reference: motion to: motion/sort_child_ids equal_fields: meeting_id restriction_mode: C @@ -102,10 +105,12 @@ sort_child_ids: restriction_mode: C origin_id: type: relation + reference: motion to: motion/derived_motion_ids # Note: The related motions may not be in the same meeting restriction_mode: A origin_meeting_id: # The meeting of the motion in origin_id, if any type: relation + reference: meeting to: meeting/forwarded_motion_ids restriction_mode: A derived_motion_ids: @@ -123,16 +128,17 @@ all_derived_motion_ids: # Calculated: All children (derived_motion_ids), grand identical_motion_ids: type: relation-list to: motion/identical_motion_ids - equal_fields: meeting_id restriction_mode: C state_id: type: relation + reference: motion_state to: motion_state/motion_ids required: true equal_fields: meeting_id restriction_mode: C recommendation_id: type: relation + reference: motion_state to: motion_state/motion_recommendation_ids equal_fields: meeting_id restriction_mode: E @@ -164,11 +170,13 @@ referenced_in_motion_recommendation_extension_ids: restriction_mode: C category_id: type: relation + reference: motion_category to: motion_category/motion_ids equal_fields: meeting_id restriction_mode: C block_id: type: relation + reference: motion_block to: motion_block/motion_ids equal_fields: meeting_id restriction_mode: C @@ -205,6 +213,7 @@ poll_ids: option_ids: type: relation-list to: option/content_object_id + reference: option on_delete: CASCADE equal_fields: meeting_id restriction_mode: C @@ -258,6 +267,7 @@ personal_note_ids: restriction_mode: C meeting_id: type: relation + reference: meeting to: meeting/motion_ids required: true restriction_mode: A diff --git a/collections/motion_block.yml b/collections/motion_block.yml index a6cfc083..4b650781 100644 --- a/collections/motion_block.yml +++ b/collections/motion_block.yml @@ -13,6 +13,7 @@ internal: restriction_mode: A sequential_number: type: number + sequence_scope: meeting_id description: The (positive) serial number of this model in its meeting. This number is auto-generated and read-only. read_only: true required: true @@ -45,6 +46,7 @@ projection_ids: restriction_mode: A meeting_id: type: relation + reference: meeting to: meeting/motion_block_ids required: true restriction_mode: A diff --git a/collections/motion_category.yml b/collections/motion_category.yml index 08ca913d..dcccff03 100644 --- a/collections/motion_category.yml +++ b/collections/motion_category.yml @@ -22,6 +22,7 @@ level: restriction_mode: A sequential_number: type: number + sequence_scope: meeting_id description: The (positive) serial number of this model in its meeting. This number is auto-generated and read-only. read_only: true required: true @@ -30,6 +31,7 @@ sequential_number: parent_id: type: relation + reference: motion_category to: motion_category/child_ids equal_fields: meeting_id restriction_mode: A @@ -45,6 +47,7 @@ motion_ids: restriction_mode: A meeting_id: type: relation + reference: meeting to: meeting/motion_category_ids required: true restriction_mode: A diff --git a/collections/motion_change_recommendation.yml b/collections/motion_change_recommendation.yml index 5305628a..ce99878b 100644 --- a/collections/motion_change_recommendation.yml +++ b/collections/motion_change_recommendation.yml @@ -42,6 +42,7 @@ creation_time: motion_id: type: relation + reference: motion to: motion/change_recommendation_ids required: true equal_fields: meeting_id @@ -49,6 +50,7 @@ motion_id: constant: true meeting_id: type: relation + reference: meeting to: meeting/motion_change_recommendation_ids required: true restriction_mode: A diff --git a/collections/motion_comment.yml b/collections/motion_comment.yml index 71b69979..c20bdea4 100644 --- a/collections/motion_comment.yml +++ b/collections/motion_comment.yml @@ -10,6 +10,7 @@ comment: motion_id: type: relation + reference: motion to: motion/comment_ids required: true equal_fields: meeting_id @@ -17,6 +18,7 @@ motion_id: constant: true section_id: type: relation + reference: motion_comment_section to: motion_comment_section/comment_ids required: true equal_fields: meeting_id @@ -24,6 +26,7 @@ section_id: constant: true meeting_id: type: relation + reference: meeting to: meeting/motion_comment_ids required: true restriction_mode: A diff --git a/collections/motion_comment_section.yml b/collections/motion_comment_section.yml index 9ac9114e..a73dc41a 100644 --- a/collections/motion_comment_section.yml +++ b/collections/motion_comment_section.yml @@ -14,6 +14,7 @@ weight: restriction_mode: A sequential_number: type: number + sequence_scope: meeting_id description: The (positive) serial number of this model in its meeting. This number is auto-generated and read-only. read_only: true required: true @@ -41,6 +42,7 @@ write_group_ids: restriction_mode: A meeting_id: type: relation + reference: meeting to: meeting/motion_comment_section_ids required: true restriction_mode: A diff --git a/collections/motion_editor.yml b/collections/motion_editor.yml index 79b9910a..1a520b35 100644 --- a/collections/motion_editor.yml +++ b/collections/motion_editor.yml @@ -9,10 +9,12 @@ weight: restriction_mode: A meeting_user_id: type: relation + reference: meeting_user to: meeting_user/motion_editor_ids restriction_mode: A motion_id: type: relation + reference: motion to: motion/editor_ids equal_fields: meeting_id restriction_mode: A @@ -20,6 +22,7 @@ motion_id: constant: true meeting_id: type: relation + reference: meeting to: meeting/motion_editor_ids required: true restriction_mode: A diff --git a/collections/motion_state.yml b/collections/motion_state.yml index c386844b..b24e196f 100644 --- a/collections/motion_state.yml +++ b/collections/motion_state.yml @@ -87,6 +87,7 @@ state_button_label: restriction_mode: A submitter_withdraw_state_id: type: relation + reference: motion_state to: motion_state/submitter_withdraw_back_ids equal_fields: - meeting_id @@ -127,6 +128,7 @@ motion_recommendation_ids: restriction_mode: A workflow_id: type: relation + reference: motion_workflow to: motion_workflow/state_ids required: true equal_fields: meeting_id @@ -139,8 +141,10 @@ first_state_of_workflow_id: restriction_mode: A meeting_id: type: relation + reference: meeting to: meeting/motion_state_ids required: true restriction_mode: A constant: true + deferred: true diff --git a/collections/motion_submitter.yml b/collections/motion_submitter.yml index 7e0d1278..43e0dad2 100644 --- a/collections/motion_submitter.yml +++ b/collections/motion_submitter.yml @@ -9,10 +9,12 @@ weight: restriction_mode: A meeting_user_id: type: relation + reference: meeting_user to: meeting_user/motion_submitter_ids restriction_mode: A motion_id: type: relation + reference: motion to: motion/submitter_ids equal_fields: meeting_id restriction_mode: A @@ -20,6 +22,7 @@ motion_id: constant: true meeting_id: type: relation + reference: meeting to: meeting/motion_submitter_ids required: true restriction_mode: A diff --git a/collections/motion_supporter.yml b/collections/motion_supporter.yml index 07bd8acc..28b0b675 100644 --- a/collections/motion_supporter.yml +++ b/collections/motion_supporter.yml @@ -6,10 +6,12 @@ id: meeting_user_id: type: relation + reference: meeting_user to: meeting_user/motion_supporter_ids restriction_mode: A motion_id: type: relation + reference: motion to: motion/supporter_ids equal_fields: meeting_id restriction_mode: A @@ -17,6 +19,7 @@ motion_id: constant: true meeting_id: type: relation + reference: meeting to: meeting/motion_supporter_ids required: true restriction_mode: A diff --git a/collections/motion_workflow.yml b/collections/motion_workflow.yml index 69d91aba..7fffccef 100644 --- a/collections/motion_workflow.yml +++ b/collections/motion_workflow.yml @@ -10,6 +10,7 @@ name: restriction_mode: A sequential_number: type: number + sequence_scope: meeting_id description: The (positive) serial number of this model in its meeting. This number is auto-generated and read-only. read_only: true required: true @@ -24,6 +25,7 @@ state_ids: restriction_mode: A first_state_id: type: relation + reference: motion_state to: motion_state/first_state_of_workflow_id required: true equal_fields: meeting_id @@ -38,6 +40,7 @@ default_amendment_workflow_meeting_id: restriction_mode: A meeting_id: type: relation + reference: meeting to: meeting/motion_workflow_ids required: true restriction_mode: A diff --git a/collections/motion_working_group_speaker.yml b/collections/motion_working_group_speaker.yml index 29a2ad4c..b7dd07a2 100644 --- a/collections/motion_working_group_speaker.yml +++ b/collections/motion_working_group_speaker.yml @@ -9,10 +9,12 @@ weight: restriction_mode: A meeting_user_id: type: relation + reference: meeting_user to: meeting_user/motion_working_group_speaker_ids restriction_mode: A motion_id: type: relation + reference: motion to: motion/working_group_speaker_ids equal_fields: meeting_id restriction_mode: A @@ -20,6 +22,7 @@ motion_id: constant: true meeting_id: type: relation + reference: meeting to: meeting/motion_working_group_speaker_ids required: true restriction_mode: A diff --git a/collections/option.yml b/collections/option.yml index ad21d120..015a5d79 100644 --- a/collections/option.yml +++ b/collections/option.yml @@ -24,18 +24,21 @@ abstain: poll_id: type: relation to: poll/option_ids + reference: poll equal_fields: meeting_id restriction_mode: A constant: true used_as_global_option_in_poll_id: type: relation to: poll/global_option_id + reference: poll equal_fields: meeting_id restriction_mode: A constant: true vote_ids: type: relation-list to: vote/option_id + reference: vote on_delete: CASCADE equal_fields: meeting_id restriction_mode: A @@ -45,12 +48,17 @@ content_object_id: - motion/option_ids - user/option_ids - poll_candidate_list/option_id + reference: + - motion + - user + - poll_candidate_list equal_fields: meeting_id restriction_mode: A constant: true meeting_id: type: relation to: meeting/option_ids + reference: meeting required: true restriction_mode: A constant: true diff --git a/collections/organization.yml b/collections/organization.yml index 984507f6..31da5568 100644 --- a/collections/organization.yml +++ b/collections/organization.yml @@ -27,6 +27,7 @@ reset_password_verbose_errors: gender_ids: type: relation-list to: gender/organization_id + reference: gender restriction_mode: A disable_forward_with_attachments: type: boolean @@ -96,17 +97,11 @@ saml_metadata_sp: saml_private_key: type: text restriction_mode: D - -vote_decrypt_public_main_key: - type: string - description: Public key from vote decrypt to validate cryptographic votes. - calculated: true - restriction_mode: A - committee_ids: type: relation-list - restriction_mode: B to: committee/organization_id + restriction_mode: B + reference: committee active_meeting_ids: type: relation-list restriction_mode: A @@ -123,15 +118,18 @@ organization_tag_ids: type: relation-list restriction_mode: A to: organization_tag/organization_id + reference: organization_tag theme_id: type: relation required: true restriction_mode: A to: theme/theme_for_organization_id + reference: theme theme_ids: type: relation-list restriction_mode: A to: theme/organization_id + reference: theme mediafile_ids: type: relation-list to: mediafile/owner_id @@ -145,6 +143,7 @@ user_ids: type: relation-list restriction_mode: C to: user/organization_id + reference: user users_email_sender: type: string default: OpenSlides diff --git a/collections/organization_tag.yml b/collections/organization_tag.yml index b066dd60..d884c360 100644 --- a/collections/organization_tag.yml +++ b/collections/organization_tag.yml @@ -23,6 +23,7 @@ tagged_ids: restriction_mode: A organization_id: type: relation + reference: organization to: organization/organization_tag_ids restriction_mode: A required: true diff --git a/collections/personal_note.yml b/collections/personal_note.yml index 2f53e880..6bdb95b6 100644 --- a/collections/personal_note.yml +++ b/collections/personal_note.yml @@ -13,6 +13,7 @@ star: meeting_user_id: type: relation + reference: meeting_user to: meeting_user/personal_note_ids equal_fields: meeting_id restriction_mode: A @@ -20,6 +21,8 @@ meeting_user_id: constant: true content_object_id: type: generic-relation + reference: + - motion to: collections: - motion @@ -29,6 +32,7 @@ content_object_id: constant: true meeting_id: type: relation + reference: meeting to: meeting/personal_note_ids required: true restriction_mode: A diff --git a/collections/point_of_order_category.yml b/collections/point_of_order_category.yml index b6fb3e30..e08a08b1 100644 --- a/collections/point_of_order_category.yml +++ b/collections/point_of_order_category.yml @@ -14,6 +14,7 @@ rank: restriction_mode: A meeting_id: type: relation + reference: meeting to: meeting/point_of_order_category_ids required: true restriction_mode: A diff --git a/collections/poll.yml b/collections/poll.yml index cd40c4b0..3c4bd776 100644 --- a/collections/poll.yml +++ b/collections/poll.yml @@ -4,13 +4,13 @@ id: constant: true required: true -description: - type: text - restriction_mode: A title: type: string required: true restriction_mode: A +description: + type: string + restriction_mode: A type: type: string required: true @@ -115,33 +115,18 @@ live_votes: restriction_mode: C sequential_number: type: number + sequence_scope: meeting_id description: The (positive) serial number of this model in its meeting. This number is auto-generated and read-only. read_only: true required: true restriction_mode: A constant: true -crypt_key: - type: string - description: base64 public key to cryptographic votes. - read_only: true - restriction_mode: A -crypt_signature: - type: string - description: base64 signature of cryptographic_key. - read_only: true - restriction_mode: A -votes_raw: - type: text - description: original form of decrypted votes. - read_only: true - restriction_mode: B -votes_signature: - type: string - description: base64 signature of votes_raw field. - read_only: true - restriction_mode: B content_object_id: type: generic-relation + reference: + - motion + - assignment + - topic to: collections: - motion @@ -155,12 +140,14 @@ content_object_id: option_ids: type: relation-list to: option/poll_id + reference: option on_delete: CASCADE equal_fields: meeting_id restriction_mode: A global_option_id: type: relation to: option/used_as_global_option_in_poll_id + reference: option on_delete: CASCADE equal_fields: meeting_id restriction_mode: A @@ -183,6 +170,7 @@ projection_ids: meeting_id: type: relation to: meeting/poll_ids + reference: meeting restriction_mode: A required: true constant: true diff --git a/collections/poll_candidate.yml b/collections/poll_candidate.yml index 8d90a3d3..3b818902 100644 --- a/collections/poll_candidate.yml +++ b/collections/poll_candidate.yml @@ -7,6 +7,7 @@ id: poll_candidate_list_id: type: relation to: poll_candidate_list/poll_candidate_ids + reference: poll_candidate_list equal_fields: meeting_id restriction_mode: A required: true @@ -14,6 +15,7 @@ poll_candidate_list_id: user_id: type: relation to: user/poll_candidate_ids + reference: user restriction_mode: A constant: true weight: @@ -23,6 +25,7 @@ weight: meeting_id: type: relation to: meeting/poll_candidate_ids + reference: meeting required: true restriction_mode: A constant: true diff --git a/collections/poll_candidate_list.yml b/collections/poll_candidate_list.yml index d881b754..b59799e9 100644 --- a/collections/poll_candidate_list.yml +++ b/collections/poll_candidate_list.yml @@ -7,12 +7,14 @@ id: poll_candidate_ids: type: relation-list to: poll_candidate/poll_candidate_list_id + reference: poll_candidate on_delete: CASCADE restriction_mode: A equal_fields: meeting_id meeting_id: type: relation to: meeting/poll_candidate_list_ids + reference: meeting required: true restriction_mode: A constant: true diff --git a/collections/projection.yml b/collections/projection.yml index 1663fc13..5c54a267 100644 --- a/collections/projection.yml +++ b/collections/projection.yml @@ -25,21 +25,36 @@ content: current_projector_id: type: relation + reference: projector to: projector/current_projection_ids equal_fields: meeting_id restriction_mode: A preview_projector_id: type: relation + reference: projector to: projector/preview_projection_ids equal_fields: meeting_id restriction_mode: A history_projector_id: type: relation + reference: projector to: projector/history_projection_ids equal_fields: meeting_id restriction_mode: A content_object_id: type: generic-relation + reference: + - meeting + - motion + - meeting_mediafile + - list_of_speakers + - motion_block + - assignment + - agenda_item + - topic + - poll + - projector_message + - projector_countdown to: - meeting/projection_ids - motion/projection_ids @@ -58,6 +73,7 @@ content_object_id: constant: true meeting_id: type: relation + reference: meeting to: meeting/all_projection_ids required: true restriction_mode: A diff --git a/collections/projector.yml b/collections/projector.yml index c4f64c2b..d1186f54 100644 --- a/collections/projector.yml +++ b/collections/projector.yml @@ -89,6 +89,7 @@ show_clock: restriction_mode: A sequential_number: type: number + sequence_scope: meeting_id description: The (positive) serial number of this model in its meeting. This number is auto-generated and read-only. read_only: true required: true @@ -119,62 +120,77 @@ used_as_reference_projector_meeting_id: restriction_mode: A used_as_default_projector_for_agenda_item_list_in_meeting_id: type: relation + reference: meeting to: meeting/default_projector_agenda_item_list_ids restriction_mode: A used_as_default_projector_for_topic_in_meeting_id: type: relation + reference: meeting to: meeting/default_projector_topic_ids restriction_mode: A used_as_default_projector_for_list_of_speakers_in_meeting_id: type: relation + reference: meeting to: meeting/default_projector_list_of_speakers_ids restriction_mode: A used_as_default_projector_for_current_los_in_meeting_id: type: relation + reference: meeting to: meeting/default_projector_current_los_ids restriction_mode: A used_as_default_projector_for_motion_in_meeting_id: type: relation + reference: meeting to: meeting/default_projector_motion_ids restriction_mode: A used_as_default_projector_for_amendment_in_meeting_id: type: relation + reference: meeting to: meeting/default_projector_amendment_ids restriction_mode: A used_as_default_projector_for_motion_block_in_meeting_id: type: relation + reference: meeting to: meeting/default_projector_motion_block_ids restriction_mode: A used_as_default_projector_for_assignment_in_meeting_id: type: relation + reference: meeting to: meeting/default_projector_assignment_ids restriction_mode: A used_as_default_projector_for_mediafile_in_meeting_id: type: relation + reference: meeting to: meeting/default_projector_mediafile_ids restriction_mode: A used_as_default_projector_for_message_in_meeting_id: type: relation + reference: meeting to: meeting/default_projector_message_ids restriction_mode: A used_as_default_projector_for_countdown_in_meeting_id: type: relation + reference: meeting to: meeting/default_projector_countdown_ids restriction_mode: A used_as_default_projector_for_assignment_poll_in_meeting_id: type: relation + reference: meeting to: meeting/default_projector_assignment_poll_ids restriction_mode: A used_as_default_projector_for_motion_poll_in_meeting_id: type: relation + reference: meeting to: meeting/default_projector_motion_poll_ids restriction_mode: A used_as_default_projector_for_poll_in_meeting_id: type: relation + reference: meeting to: meeting/default_projector_poll_ids restriction_mode: A meeting_id: type: relation + reference: meeting to: meeting/projector_ids restriction_mode: A required: true diff --git a/collections/projector_countdown.yml b/collections/projector_countdown.yml index b30a299e..682e7064 100644 --- a/collections/projector_countdown.yml +++ b/collections/projector_countdown.yml @@ -40,6 +40,7 @@ used_as_poll_countdown_meeting_id: restriction_mode: A meeting_id: type: relation + reference: meeting to: meeting/projector_countdown_ids restriction_mode: A required: true diff --git a/collections/projector_message.yml b/collections/projector_message.yml index 5a2ac13f..71e32250 100644 --- a/collections/projector_message.yml +++ b/collections/projector_message.yml @@ -16,6 +16,7 @@ projection_ids: restriction_mode: A meeting_id: type: relation + reference: meeting to: meeting/projector_message_ids restriction_mode: A required: true diff --git a/collections/speaker.yml b/collections/speaker.yml index f39519b5..e34a2fe7 100644 --- a/collections/speaker.yml +++ b/collections/speaker.yml @@ -47,6 +47,7 @@ point_of_order: list_of_speakers_id: type: relation + reference: list_of_speakers to: list_of_speakers/speaker_ids required: true equal_fields: meeting_id @@ -56,19 +57,23 @@ structure_level_list_of_speakers_id: type: relation equal_fields: meeting_id restriction_mode: A + reference: structure_level_list_of_speakers to: structure_level_list_of_speakers/speaker_ids meeting_user_id: type: relation + reference: meeting_user to: meeting_user/speaker_ids equal_fields: meeting_id restriction_mode: A point_of_order_category_id: type: relation + reference: point_of_order_category to: point_of_order_category/speaker_ids equal_fields: meeting_id restriction_mode: A meeting_id: type: relation + reference: meeting to: meeting/speaker_ids required: true restriction_mode: A diff --git a/collections/structure_level.yml b/collections/structure_level.yml index 6a5fad67..83e11d65 100644 --- a/collections/structure_level.yml +++ b/collections/structure_level.yml @@ -28,6 +28,7 @@ structure_level_list_of_speakers_ids: meeting_id: type: relation required: true + reference: meeting to: meeting/structure_level_ids restriction_mode: A diff --git a/collections/structure_level_list_of_speakers.yml b/collections/structure_level_list_of_speakers.yml index be54bf51..17404bd5 100644 --- a/collections/structure_level_list_of_speakers.yml +++ b/collections/structure_level_list_of_speakers.yml @@ -9,12 +9,14 @@ structure_level_id: required: true equal_fields: meeting_id restriction_mode: A + reference: structure_level to: structure_level/structure_level_list_of_speakers_ids list_of_speakers_id: type: relation required: true equal_fields: meeting_id restriction_mode: A + reference: list_of_speakers to: list_of_speakers/structure_level_list_of_speakers_ids speaker_ids: type: relation-list @@ -42,6 +44,7 @@ current_start_time: description: The current start time of a speaker for this structure_level. Is only set if a currently speaking speaker exists meeting_id: type: relation + reference: meeting to: meeting/structure_level_list_of_speakers_ids required: true restriction_mode: A diff --git a/collections/tag.yml b/collections/tag.yml index ce371dc7..efb38085 100644 --- a/collections/tag.yml +++ b/collections/tag.yml @@ -21,6 +21,7 @@ tagged_ids: restriction_mode: A meeting_id: type: relation + reference: meeting to: meeting/tag_ids required: true restriction_mode: A diff --git a/collections/theme.yml b/collections/theme.yml index 2096fd27..5d0bb7e7 100644 --- a/collections/theme.yml +++ b/collections/theme.yml @@ -154,9 +154,9 @@ theme_for_organization_id: to: organization/theme_id type: relation organization_id: - required: true - restriction_mode: A - to: organization/theme_ids type: relation - constant: true + reference: organization + to: organization/theme_ids + restriction_mode: A + required: true diff --git a/collections/topic.yml b/collections/topic.yml index 8f5c85f3..6162e1bf 100644 --- a/collections/topic.yml +++ b/collections/topic.yml @@ -13,6 +13,7 @@ text: restriction_mode: A sequential_number: type: number + sequence_scope: meeting_id description: The (positive) serial number of this model in its meeting. This number is auto-generated and read-only. read_only: true required: true @@ -54,6 +55,7 @@ projection_ids: restriction_mode: A meeting_id: type: relation + reference: meeting to: meeting/topic_ids required: true restriction_mode: A diff --git a/collections/user.yml b/collections/user.yml index 2041889f..b1c372c7 100644 --- a/collections/user.yml +++ b/collections/user.yml @@ -72,6 +72,7 @@ gender_id: type: relation to: gender/user_ids restriction_mode: A + reference: gender # Organization, meeting and committee organization_management_level: @@ -89,14 +90,39 @@ is_present_in_meeting_ids: # Calculates all committee's where the user is # - committee-manager (= committees in committee_management_ids) - # - is member (= has group-rights) of a meeting in the committee + # - is member (= is in a group) of a meeting in the committee + # - or has his home committee committee_ids: type: relation-list to: committee/user_ids restriction_mode: E read_only: true - description: "Calculated field." + description: "Calculated field: Returns committee_ids, where the user is manager or member in a meeting" + sql: | + ( + SELECT array_agg(DISTINCT ci.committee_id ORDER BY ci.committee_id) + FROM ( + -- Select committee_ids from meetings the user is part of + SELECT m.committee_id + FROM meeting_user_t AS mu + INNER JOIN meeting_t AS m ON m.id = mu.meeting_id + WHERE mu.user_id = u.id + UNION + + -- Select committee_ids from committee managers + SELECT cmu.committee_id + FROM nm_committee_manager_ids_user_t cmu + WHERE cmu.user_id = u.id + + UNION + + -- Select home_committee_id from user + SELECT u_hc.home_committee_id + FROM user_t u_hc + WHERE u_hc.home_committee_id IS NOT NULL AND u_hc.id = u.id + ) AS ci + ) AS committee_ids # committee specific permissions committee_management_ids: type: relation-list @@ -115,23 +141,28 @@ poll_voted_ids: option_ids: type: relation-list to: option/content_object_id + reference: option restriction_mode: A vote_ids: type: relation-list to: vote/user_id + reference: vote restriction_mode: A delegated_vote_ids: type: relation-list to: vote/delegated_user_id + reference: vote restriction_mode: A poll_candidate_ids: type: relation-list to: poll_candidate/user_id + reference: poll_candidate restriction_mode: A home_committee_id: type: relation to: committee/native_user_ids restriction_mode: E + reference: committee history_position_ids: type: relation-list @@ -143,12 +174,20 @@ history_entry_ids: restriction_mode: A meeting_ids: - type: number[] - description: Calculated. All ids from meetings calculated via meeting_user and group_ids as integers. + type: relation-list + description: Calculated. All ids from meetings calculated via meeting_user. read_only: true + sql: | + ( + SELECT array_agg(DISTINCT mu.meeting_id ORDER BY mu.meeting_id) + FROM meeting_user_t mu + WHERE mu.user_id = u.id + ) AS meeting_ids + to: meeting/user_ids restriction_mode: E organization_id: type: relation + reference: organization to: organization/user_ids required: true restriction_mode: F diff --git a/collections/vote.yml b/collections/vote.yml index 76481dc3..fae6746f 100644 --- a/collections/vote.yml +++ b/collections/vote.yml @@ -21,6 +21,7 @@ user_token: option_id: type: relation to: option/vote_ids + reference: option equal_fields: meeting_id required: true restriction_mode: A @@ -28,14 +29,17 @@ option_id: user_id: type: relation to: user/vote_ids + reference: user restriction_mode: A delegated_user_id: type: relation to: user/delegated_vote_ids + reference: user restriction_mode: A meeting_id: type: relation to: meeting/vote_ids + reference: meeting required: true restriction_mode: A constant: true diff --git a/dev/Dockerfile b/dev/Dockerfile index 20f1b85d..ec632ec8 100644 --- a/dev/Dockerfile +++ b/dev/Dockerfile @@ -1,11 +1,15 @@ -FROM python:3.10.13-slim-bookworm +FROM python:3.12.9-slim-bookworm -RUN apt-get update && apt-get install --yes make bash-completion +RUN apt-get update && apt-get install --yes make bash-completion postgresql-client WORKDIR /app/dev/ COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt +EXPOSE 5678 + STOPSIGNAL SIGKILL + +ENTRYPOINT ["scripts/entrypoint.sh"] CMD sleep infinity diff --git a/dev/Dockerfile-postgres b/dev/Dockerfile-postgres new file mode 100644 index 00000000..15a54805 --- /dev/null +++ b/dev/Dockerfile-postgres @@ -0,0 +1,8 @@ +FROM postgres:15 + +# Install the postgresql debugger +RUN apt-get update +RUN apt-get install -y --no-install-recommends apt-utils +RUN apt-get install -y --no-install-recommends postgresql-15-pldebugger + +EXPOSE 5432 diff --git a/dev/Makefile b/dev/Makefile deleted file mode 100644 index a3638a30..00000000 --- a/dev/Makefile +++ /dev/null @@ -1,42 +0,0 @@ -# Commands inside the container - -all: pyupgrade black autoflake isort flake8 mypy - -pyupgrade: - pyupgrade --py310-plus --exit-zero-even-if-changed $$(find . -name '*.py') - -check-pyupgrade: - pyupgrade --py310-plus $$(find . -name '*.py') - -black: - black src/ - -check-black: - black --check --diff src/ - -autoflake: - autoflake src/ - -isort: - isort src/ - -check-isort: - isort --check-only --diff src/ - -flake8: - flake8 src/ - -mypy: - mypy src/ - -validate-models: - python src/validate.py - -# Docker manage commands - -run-dev: - USER_ID=$$(id -u $${USER}) GROUP_ID=$$(id -g $${USER}) docker compose up -d --build - docker compose exec models bash --rcfile /etc/bash_completion - -stop-dev: - docker compose down diff --git a/dev/doc/generate_sql_schema.md b/dev/doc/generate_sql_schema.md new file mode 100644 index 00000000..58183ac0 --- /dev/null +++ b/dev/doc/generate_sql_schema.md @@ -0,0 +1,43 @@ +# Readme for relational sql schema + +All names used in postgres are limited to 63 characters per name. This can only be changed by compiling the source code of postgres. +The length will be checked by the following methods on generating the sql-schema. + +## The type TableFieldType + +This type is used sometimes for the parameters of the methods. It can be build by it's constructor or as convinience with the static method **get_definitions_from_foreign**, which takes as parameters +the str-values from the **to**- and the **reference**-Attribut from models field description. + +It contains the **table_name**, **fname** for the field name, the dictionary **tfield** from the models.yml with all attributes and a **ref_column**, usually filled with **id** for the foreign-key definitions. +As convinience property there is the collectionfield as combination of table_name/field_name. + +## Naming conventions for tables and depending views as used in models.yml + +To get the table name use method **get_table_name** with the parameter of the collection name from the models.yml. The resulting name has a **T** for table to distinguish it from the view. + +To get the view name use the method **get_view_name** with the parameter of the collection name from the models.yml. The view name is identical with that from models.yml, except for **group and user**, which are reserved names in sql. They will get an appended **_** to their name. + + +## Naming conventions for intermediate tables and views + +### relation-list versus relation-list + +Because the base tables of these intermediate files are symmetric, the parts of the tables name are taken in an alphabetical order of their **collectionfields**. The name is build from parts + +* nm_ Constant part to mark a n:m intermediate table +* table_name of the smaller **collectionfield** +* _ Constant divider +* field name of the smaller **collectionfield** +* _ Constant divider +* table name of the greater **collectionfield** + +### generic-relation-list versus relation-list + +The name of the intermediate table for **generic-relation-list** versus **relation-list** is always build from the generic-relation-lists side. + +* gm_ Constant part to mark a genericc-list:m intermediate table +* table name of the generic-relation-list field +* _ Constant divider +* field name of the generic-relation-list field + +## Attributes and rules \ No newline at end of file diff --git a/dev/docker-compose.yml b/dev/docker-compose.yml index dc885f8f..f7d0cd87 100644 --- a/dev/docker-compose.yml +++ b/dev/docker-compose.yml @@ -3,14 +3,25 @@ services: models: build: . user: $USER_ID:$GROUP_ID + ports: + - 5678:5678 volumes: - ..:/app + environment: + - DATABASE_HOST=postgres + - DATABASE_PORT=5432 + - DATABASE_USER=openslides + - DATABASE_NAME=openslides + - PGPASSWORD=openslides + depends_on: - postgres networks: - postgres postgres: - image: postgres:15 + build: + context: . + dockerfile: Dockerfile-postgres environment: - POSTGRES_USER=openslides - POSTGRES_PASSWORD=openslides diff --git a/dev/requirements.txt b/dev/requirements.txt index 6914a5e8..8d283236 100644 --- a/dev/requirements.txt +++ b/dev/requirements.txt @@ -2,12 +2,18 @@ autoflake==2.3.1 black==25.12.0 flake8==7.3.0 isort==7.0.0 +sqlfluff==3.4.2 mypy==1.19.1 pytest==9.0.2 pyupgrade==3.21.2 pyyaml==6.0.3 simplejson==3.20.2 +debugpy==1.8.0 +requests==2.31.0 +psycopg[binary]==3.2.6 +python-sql==1.5.1 # typing types-PyYAML==6.0.12.20250915 types-simplejson==3.20.0.20250822 +types-requests==2.31.0.20240125 diff --git a/dev/scripts/apply_data.sh b/dev/scripts/apply_data.sh new file mode 100755 index 00000000..a49829a6 --- /dev/null +++ b/dev/scripts/apply_data.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +cd "$(dirname "$0")" +psql -1 -v ON_ERROR_STOP=1 -h "$DATABASE_HOST" -p "$DATABASE_PORT" -U "$DATABASE_USER" -d "$DATABASE_NAME" -f ../sql/$1 diff --git a/dev/scripts/apply_db_schema.sh b/dev/scripts/apply_db_schema.sh new file mode 100755 index 00000000..59d66d12 --- /dev/null +++ b/dev/scripts/apply_db_schema.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +cd "$(dirname "$0")" +psql -1 -v ON_ERROR_STOP=1 -h "$DATABASE_HOST" -p "$DATABASE_PORT" -U "$DATABASE_USER" -d "$DATABASE_NAME" -f ../sql/schema_relational.sql diff --git a/dev/scripts/entrypoint.sh b/dev/scripts/entrypoint.sh new file mode 100755 index 00000000..a24dce57 --- /dev/null +++ b/dev/scripts/entrypoint.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +scripts/wait-for-database.sh +scripts/apply_db_schema.sh + +exec "$@" diff --git a/dev/scripts/wait-for-database.sh b/dev/scripts/wait-for-database.sh new file mode 100755 index 00000000..b6110ff3 --- /dev/null +++ b/dev/scripts/wait-for-database.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +until pg_isready -h "$DATABASE_HOST" -p "$DATABASE_PORT" -U "$DATABASE_USER"; do + echo "Waiting for Postgres server '$DATABASE_HOST' to become available..." + sleep 3 +done diff --git a/dev/sql/base_data.sql b/dev/sql/base_data.sql new file mode 100644 index 00000000..256fe692 --- /dev/null +++ b/dev/sql/base_data.sql @@ -0,0 +1,167 @@ +-- This script can only be used for an empty database without used sequences. +INSERT INTO +theme_t (id, name) +VALUES +(1, 'standard theme'); +-- Increase sequence of theme_t.id to avoid errors. +SELECT nextval('theme_t_id_seq'); + +INSERT INTO +organization_t (name, theme_id) +VALUES +('Intevation', 1); + +INSERT INTO +committee_t (id, name) +VALUES +(1, 'committee'); +-- Increase sequence of committee_t.id to avoid errors. +SELECT nextval('committee_t_id_seq'); + +INSERT INTO gender_t (name) VALUES ('female'); + + +BEGIN; +INSERT INTO +meeting_t ( + id, + default_group_id, + admin_group_id, + motions_default_workflow_id, + motions_default_amendment_workflow_id, + committee_id, + reference_projector_id, + name +) +VALUES +(1, 1, 2, 1, 1, 1, 1, 'meeting'); +-- Increase sequence of committee_t.id to avoid errors. +SELECT nextval('meeting_t_id_seq'); + +INSERT INTO +group_t (id, name, meeting_id, permissions) +VALUES +( + 1, + 'Default', + 1, + '{ + "agenda_item.can_see", + "assignment.can_see", + "meeting.can_see_autopilot", + "meeting.can_see_frontpage", + "motion.can_see", + "projector.can_see" +}' +), +(2, 'Admin', 1, DEFAULT); +-- Set sequence of group_t.id to avoid errors. +SELECT setval('group_t_id_seq', 2); + +INSERT INTO +motion_workflow_t ( + id, + name, + first_state_id, + meeting_id +) +VALUES +(1, 'Simple Workflow', 1, 1); +-- Increase sequence of motion_workflow_t.id to avoid errors. +SELECT nextval('motion_workflow_t_id_seq'); + +INSERT INTO +motion_state_t ( + id, + name, + weight, + workflow_id, + meeting_id, + allow_create_poll, + allow_support, + set_workflow_timestamp, + recommendation_label, + css_class, + merge_amendment_into_final +) +VALUES +( + 1, + 'submitted', + 1, + 1, + 1, + true, + true, + true, + 'Submitted', + 'grey', + 'do_not_merge' +), +( + 2, + 'accepted', + 2, + 1, + 1, + DEFAULT, + DEFAULT, + DEFAULT, + 'Acceptance', + 'green', + 'do_merge' +), +( + 3, + 'rejected', + 3, + 1, + 1, + DEFAULT, + DEFAULT, + DEFAULT, + 'Rejection', + 'red', + 'do_not_merge' +), +( + 4, + 'not decided', + 4, + 1, + 1, + DEFAULT, + DEFAULT, + DEFAULT, + 'No decision', + 'grey', + 'do_not_merge' +); +-- Set sequence of motion_state_t.id to avoid errors. +SELECT setval('motion_state_t_id_seq', 4); + + +INSERT INTO +projector_t +( + id, + meeting_id, + used_as_default_projector_for_agenda_item_list_in_meeting_id, + used_as_default_projector_for_topic_in_meeting_id, + used_as_default_projector_for_list_of_speakers_in_meeting_id, + used_as_default_projector_for_current_los_in_meeting_id, + used_as_default_projector_for_motion_in_meeting_id, + used_as_default_projector_for_amendment_in_meeting_id, + used_as_default_projector_for_motion_block_in_meeting_id, + used_as_default_projector_for_assignment_in_meeting_id, + used_as_default_projector_for_mediafile_in_meeting_id, + used_as_default_projector_for_message_in_meeting_id, + used_as_default_projector_for_countdown_in_meeting_id, + used_as_default_projector_for_assignment_poll_in_meeting_id, + used_as_default_projector_for_motion_poll_in_meeting_id, + used_as_default_projector_for_poll_in_meeting_id +) +VALUES (1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1); +SELECT nextval('projector_t_id_seq'); + +COMMIT; diff --git a/dev/sql/schema_relational.sql b/dev/sql/schema_relational.sql new file mode 100644 index 00000000..a6ae677f --- /dev/null +++ b/dev/sql/schema_relational.sql @@ -0,0 +1,3991 @@ + +-- schema_relational.sql for initial database setup OpenSlides +-- Code generated. DO NOT EDIT. +-- MODELS_YML_CHECKSUM = 'e0d5579236d105d49e687ac5999f0184' + + +-- Function and meta table definitions + +CREATE EXTENSION hstore; -- included in standard postgres-installations, check for alpine + +CREATE FUNCTION generate_sequence() +RETURNS trigger +AS $sequences_trigger$ +-- Creates a sequence for the id given by depend_field NEW data if it doesn't exist. +-- Writes the next value to for this sequence to NEW. +-- In case a number is given in actual_column of the NEW record that is used +-- and the corresponding sequence increased if necessary. +-- Usage with 3 parameters IN TRIGGER DEFINITION: +-- table_name: table this is treated for +-- actual_column: column that will be filled with the actual value +-- depend_field: field that differentiates the sequences. usually meeting_id +DECLARE + table_name TEXT := TG_ARGV[0]; + actual_column TEXT := TG_ARGV[1]; + depend_field TEXT := TG_ARGV[2]; + depend_field_id INTEGER; + sequence_name TEXT; + sequence_value INTEGER; + sequence_max INTEGER; +BEGIN + depend_field_id := hstore(NEW) -> (depend_field); + sequence_name := table_name || '_' || depend_field || depend_field_id || '_' || actual_column || '_seq'; + EXECUTE format('CREATE SEQUENCE IF NOT EXISTS %I OWNED BY %I.%I', sequence_name, table_name, actual_column); + sequence_value := hstore(NEW) -> actual_column; + IF sequence_value IS NULL THEN + sequence_value := nextval(sequence_name); + ELSE + EXECUTE format('SELECT last_value FROM %I', sequence_name) INTO sequence_max; + -- <= because the unused sequence starts with last_value=1 and is_called=f and needs to be written to. + IF sequence_max <= sequence_value THEN + SELECT setval(sequence_name, sequence_value) INTO sequence_value; + END IF; + END IF; + RETURN populate_record(NEW, format('%s=>%s',actual_column, sequence_value)::hstore); +END; +$sequences_trigger$ +LANGUAGE plpgsql; + +CREATE FUNCTION log_modified_models() RETURNS trigger AS $log_modified_trigger$ +DECLARE + escaped_table_name varchar; + operation_var TEXT; + fqid_var TEXT; +BEGIN + escaped_table_name := TG_ARGV[0]; + operation_var := LOWER(TG_OP); + fqid_var := escaped_table_name || '/' || NEW.id; + IF (TG_OP = 'DELETE') THEN + fqid_var := escaped_table_name || '/' || OLD.id; + END IF; + + INSERT INTO os_notify_log_t (operation, fqid, xact_id, timestamp) + VALUES (operation_var, fqid_var, pg_current_xact_id(), 'now') + ON CONFLICT (operation,fqid,xact_id) DO NOTHING; + RETURN NULL; -- AFTER TRIGGER needs no return +END; +$log_modified_trigger$ LANGUAGE plpgsql; + +CREATE FUNCTION check_unique_ids_pair() +RETURNS trigger +AS $unique_ids_pair_trigger$ +-- usage with 1 parameter IN TRIGGER DEFINITION: +-- base_column_name: name of write fields before adding numeric suffixes +-- Guards against mirrored duplicates by skipping one of the pairs. +DECLARE + base_column_name text; + value_1 integer; + value_2 integer; +BEGIN + base_column_name := TG_ARGV[0]; + value_1 := hstore(NEW) -> (base_column_name || '_1'); + value_2 := hstore(NEW) -> (base_column_name || '_2'); + + IF (value_1 > value_2) THEN + RETURN NULL; + END IF; + + RETURN NEW; +END; +$unique_ids_pair_trigger$ +LANGUAGE plpgsql; + +CREATE FUNCTION notify_transaction_end() RETURNS trigger AS $notify_trigger$ +DECLARE + payload TEXT; + body_content_text TEXT; +BEGIN + -- Running the trigger for the first time in a transaction creates the table and after committing the transaction the table is dropped. + -- Every next run of the trigger in this transaction raises a notice that the table exists. Setting the log_min_messages to notice increases the noise because of such messages. + CREATE LOCAL TEMPORARY TABLE + IF NOT EXISTS tbl_notify_counter_tx_once ( + "id" integer NOT NULL PRIMARY KEY GENERATED ALWAYS AS IDENTITY + ) ON COMMIT DROP; + + -- If running for the first time, the transaction id is send via os_notify. + IF NOT EXISTS (SELECT * FROM tbl_notify_counter_tx_once) THEN + INSERT INTO tbl_notify_counter_tx_once DEFAULT VALUES; + payload := '{"xactId":' || + pg_current_xact_id() || + '}'; + PERFORM pg_notify('os_notify', payload); + END IF; + + RETURN NULL; -- AFTER TRIGGER needs no return +END; +$notify_trigger$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION log_modified_related_models() +RETURNS trigger AS $log_modified_related_trigger$ +DECLARE + fqid_var TEXT; + ref_column TEXT; + foreign_table TEXT; + foreign_id TEXT; + i INTEGER := 0; +BEGIN + + WHILE i < TG_NARGS LOOP + foreign_table := TG_ARGV[i]; + ref_column := TG_ARGV[i+1]; + + IF (TG_OP = 'DELETE') THEN + EXECUTE format('SELECT ($1).%I', ref_column) INTO foreign_id USING OLD; + ELSE + EXECUTE format('SELECT ($1).%I', ref_column) INTO foreign_id USING NEW; + END IF; + + IF foreign_id IS NOT NULL THEN + fqid_var := foreign_table || '/' || foreign_id; + INSERT INTO os_notify_log_t (operation, fqid, xact_id, timestamp) + VALUES ('update', fqid_var, pg_current_xact_id(), now()) + ON CONFLICT (operation,fqid,xact_id) DO NOTHING; + END IF; + + i := i + 2; + END LOOP; + + RETURN NULL; -- AFTER TRIGGER needs no return +END; +$log_modified_related_trigger$ LANGUAGE plpgsql; + +CREATE TABLE os_notify_log_t ( + id integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + operation varchar(32), + fqid varchar(256) NOT NULL, + xact_id xid8, + timestamp timestamptz, + CONSTRAINT unique_fqid_xact_id_operation UNIQUE (operation,fqid,xact_id) +); + +CREATE TABLE version ( + migration_index INTEGER PRIMARY KEY, + migration_state TEXT, + replace_tables JSONB +); + +CREATE OR REPLACE FUNCTION prevent_writes() RETURNS trigger AS $read_only_trigger$ +BEGIN + RAISE EXCEPTION 'Table % is currently read-only.', TG_TABLE_NAME; +END; +$read_only_trigger$ LANGUAGE plpgsql; + +CREATE FUNCTION check_not_null_for_1_1() RETURNS trigger AS $not_null_trigger$ +-- Parameters required for all operation types +-- 0. own_collection – name of the view on which the trigger is defined +-- 1. own_column – column in `own_table` referencing +-- `foreign_table` +-- +-- Parameter needed for extended error message generation for 'UPDATE' and +-- 'DELETE' (can be empty on INSERT) +-- 2. foreign_collection – name of collection of the triggered table that +-- will be used to SELECT +-- 3. foreign_column – column in the foreign table referencing +-- `own_table` +DECLARE + -- Parameters from TRIGGER DEFINITION + -- Always required + own_collection TEXT := TG_ARGV[0]; + own_column TEXT := TG_ARGV[1]; + + -- Only for TG_OP in ('UPDATE', 'DELETE') + foreign_collection TEXT := TG_ARGV[2]; + foreign_column TEXT := TG_ARGV[3]; + + -- Calculated parameters + own_id INTEGER; + foreign_id INTEGER; + counted INTEGER; + error_message TEXT; +BEGIN + IF (TG_OP = 'INSERT') THEN + -- in case of INSERT the view is checked on itself so the own id is applicable + own_id := NEW.id; + ELSE + own_id := hstore(OLD) -> foreign_column; + EXECUTE format('SELECT 1 FROM %I WHERE "id" = %L', own_collection, own_id) INTO counted; + IF (counted IS NULL) THEN + -- if the earlier referenced row was deleted (in the same transaction) we can quit. + RETURN NULL; + END IF; + END IF; + + EXECUTE format('SELECT %I FROM %I WHERE id = %L', own_column, own_collection, own_id) INTO counted; + IF (counted is NULL) THEN + error_message := format('Trigger %s: NOT NULL CONSTRAINT VIOLATED for %s/%s/%s', TG_NAME, own_collection, own_id, own_column); + IF TG_OP IN ('UPDATE', 'DELETE') THEN + foreign_id := OLD.id; + error_message := error_message || format(' from relationship before %s/%s/%s', foreign_collection, foreign_id, foreign_column); + END IF; + RAISE EXCEPTION '%', error_message; + END IF; + RETURN NULL; -- AFTER TRIGGER needs no return +END; +$not_null_trigger$ language plpgsql; + +CREATE FUNCTION check_not_null_for_1_n() RETURNS trigger AS $not_null_trigger$ +-- Parameters required for all operation types +-- 0. own_table – name of the table on which the trigger is defined +-- 1. own_column – column in `own_table` referencing +-- `foreign_table` +-- 2. foreign_table – name of the triggered table, that will be used to SELECT +-- 3. foreign_column – column in the foreign table referencing +-- `own_table` +DECLARE + -- Parameters from TRIGGER DEFINITION + -- Always required + own_table TEXT := TG_ARGV[0]; + own_column TEXT := TG_ARGV[1]; + foreign_table TEXT := TG_ARGV[2]; + foreign_column TEXT := TG_ARGV[3]; + + -- Calculated parameters + own_collection TEXT; + foreign_collection TEXT; + own_id INTEGER; + foreign_id INTEGER; + counted INTEGER; + error_message TEXT; +BEGIN + IF (TG_OP = 'INSERT') THEN + -- in case of INSERT the view is checked on itself so the own id is applicable + own_id := NEW.id; + ELSE + own_id := hstore(OLD) -> foreign_column; + EXECUTE format('SELECT 1 FROM %I WHERE "id" = %L', own_table, own_id) INTO counted; + IF (counted IS NULL) THEN + -- if the earlier referenced row was deleted (in the same transaction) we can quit. + RETURN NULL; + END IF; + END IF; + + EXECUTE format('SELECT 1 FROM %I WHERE %I = %L', foreign_table, foreign_column, own_id) INTO counted; + IF (counted is NULL) THEN + own_collection := SUBSTRING(own_table FOR LENGTH(own_table) - 2); + error_message := format('Trigger %s: NOT NULL CONSTRAINT VIOLATED for %s/%s/%s', TG_NAME, own_collection, own_id, own_column); + IF TG_OP IN ('UPDATE', 'DELETE') THEN + foreign_collection := SUBSTRING(foreign_table FOR LENGTH(foreign_table) - 2); + foreign_id := OLD.id; + error_message := error_message || format(' from relationship before %s/%s/%s', foreign_collection, foreign_id, foreign_column); + END IF; + RAISE EXCEPTION '%', error_message; + END IF; + RETURN NULL; -- AFTER TRIGGER needs no return +END; +$not_null_trigger$ language plpgsql; + +CREATE FUNCTION check_not_null_for_n_m() RETURNS trigger AS $not_null_trigger$ +-- Parameters required for both INSERT and DELETE operations +-- 0. intermediate_table_name – name of the n:m table +-- 1. own_table – name of the table on which the trigger is defined +-- 2. own_column – column in `own_table` referencing +-- `foreign_collection` +-- 3. intermediate_table_own_key – column in the n:m table referencing +-- `own_table` +-- +-- Parameters needed for extended error message generation for 'DELETE' +-- (can be empty on INSERT) +-- 4. intermediate_table_foreign_key – column in the n:m table referencing +-- the foreign table +-- 5. foreign_collection – name of the collection of the foreign table +-- 6. foreign_column – column in the foreign table referencing +-- `own_collection` +DECLARE + -- Parameters from TRIGGER DEFINITION + -- Always required + intermediate_table_name TEXT := TG_ARGV[0]; + own_table TEXT := TG_ARGV[1]; + own_column TEXT := TG_ARGV[2]; + intermediate_table_own_key TEXT := TG_ARGV[3]; + + -- Only for TG_OP = 'DELETE' + intermediate_table_foreign_key TEXT := TG_ARGV[4]; + foreign_collection TEXT := TG_ARGV[5]; + foreign_column TEXT := TG_ARGV[6]; + + -- Calculated parameters + own_collection TEXT; + own_id INTEGER; + foreign_id INTEGER; + counted INTEGER; + error_message TEXT; +BEGIN + IF (TG_OP = 'INSERT') THEN + -- in case of INSERT the view is checked on itself so the own id is applicable + own_id := NEW.id; + ELSE + own_id := hstore(OLD) -> intermediate_table_own_key; + EXECUTE format('SELECT 1 FROM %I WHERE "id" = %L', own_table, own_id) INTO counted; + IF (counted IS NULL) THEN + -- if the earlier referenced row was deleted (in the same transaction) we can quit. + RETURN NULL; + END IF; + END IF; + + EXECUTE format('SELECT 1 FROM %I WHERE %I = %L', intermediate_table_name, intermediate_table_own_key, own_id) INTO counted; + IF (counted is NULL) THEN + own_collection := SUBSTRING(own_table FOR LENGTH(own_table) - 2); + error_message := format('Trigger %s: NOT NULL CONSTRAINT VIOLATED for %s/%s/%s', TG_NAME, own_collection, own_id, own_column); + IF (TG_OP = 'DELETE') THEN + foreign_id := hstore(OLD) -> intermediate_table_foreign_key; + error_message := error_message || format(' from relationship before %s/%s/%s', foreign_collection, foreign_id, foreign_column); + END IF; + RAISE EXCEPTION '%', error_message; + END IF; + RETURN NULL; -- AFTER TRIGGER needs no return +END; +$not_null_trigger$ language plpgsql; + + +-- Table definitions + +CREATE TABLE action_worker_t ( + id integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY NOT NULL, + name varchar(256) NOT NULL, + state varchar(256) NOT NULL CONSTRAINT enum_action_worker_state CHECK (state IN ('running', 'end', 'aborted')), + created timestamptz NOT NULL, + timestamp timestamptz NOT NULL, + result jsonb, + user_id integer NOT NULL +); + + + +comment on column action_worker_t.user_id is 'Id of the calling user. If the action is called via internal route, the value will be -1.'; + + +CREATE TABLE agenda_item_t ( + id integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY NOT NULL, + item_number varchar(256), + comment varchar(256), + closed boolean DEFAULT False, + type varchar(256) CONSTRAINT enum_agenda_item_type CHECK (type IN ('common', 'internal', 'hidden')) DEFAULT 'common', + duration integer CONSTRAINT minimum_duration CHECK (duration >= 0), + is_internal boolean, + is_hidden boolean, + level integer, + weight integer, + content_object_id varchar(100) NOT NULL, + content_object_id_motion_id integer UNIQUE GENERATED ALWAYS AS (CASE WHEN split_part(content_object_id, '/', 1) = 'motion' THEN cast(split_part(content_object_id, '/', 2) AS INTEGER) ELSE null END) STORED, + content_object_id_motion_block_id integer UNIQUE GENERATED ALWAYS AS (CASE WHEN split_part(content_object_id, '/', 1) = 'motion_block' THEN cast(split_part(content_object_id, '/', 2) AS INTEGER) ELSE null END) STORED, + content_object_id_assignment_id integer UNIQUE GENERATED ALWAYS AS (CASE WHEN split_part(content_object_id, '/', 1) = 'assignment' THEN cast(split_part(content_object_id, '/', 2) AS INTEGER) ELSE null END) STORED, + content_object_id_topic_id integer UNIQUE GENERATED ALWAYS AS (CASE WHEN split_part(content_object_id, '/', 1) = 'topic' THEN cast(split_part(content_object_id, '/', 2) AS INTEGER) ELSE null END) STORED, + CONSTRAINT valid_content_object_id_part1 CHECK (split_part(content_object_id, '/', 1) IN ('motion','motion_block','assignment','topic')), + parent_id integer, + meeting_id integer NOT NULL +); + + + +comment on column agenda_item_t.duration is 'Given in seconds'; +comment on column agenda_item_t.is_internal is 'Calculated by the server'; +comment on column agenda_item_t.is_hidden is 'Calculated by the server'; +comment on column agenda_item_t.level is 'Calculated by the server'; + + +CREATE TABLE assignment_t ( + id integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY NOT NULL, + title varchar(256) NOT NULL, + description text, + open_posts integer CONSTRAINT minimum_open_posts CHECK (open_posts >= 0) DEFAULT 0, + phase varchar(256) CONSTRAINT enum_assignment_phase CHECK (phase IN ('search', 'voting', 'finished')) DEFAULT 'search', + default_poll_description text, + number_poll_candidates boolean, + sequential_number integer NOT NULL, + CONSTRAINT unique_assignment_sequential_number UNIQUE (sequential_number, meeting_id), + meeting_id integer NOT NULL +); + + + +comment on column assignment_t.sequential_number is 'The (positive) serial number of this model in its meeting. This number is auto-generated and read-only.'; + + +CREATE TABLE assignment_candidate_t ( + id integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY NOT NULL, + weight integer DEFAULT 10000, + assignment_id integer NOT NULL, + meeting_user_id integer, + meeting_id integer NOT NULL +); + + + + +CREATE TABLE chat_group_t ( + id integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY NOT NULL, + name varchar(256) NOT NULL, + weight integer DEFAULT 10000, + meeting_id integer NOT NULL +); + + + + +CREATE TABLE chat_message_t ( + id integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY NOT NULL, + content text NOT NULL, + created timestamptz NOT NULL, + meeting_user_id integer, + chat_group_id integer NOT NULL, + meeting_id integer NOT NULL +); + + + + +CREATE TABLE committee_t ( + id integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY NOT NULL, + name varchar(256) NOT NULL, + description text, + external_id varchar(256), + default_meeting_id integer UNIQUE, + parent_id integer, + organization_id integer GENERATED ALWAYS AS (1) STORED NOT NULL +); + + + +comment on column committee_t.external_id is 'unique'; + + +CREATE TABLE gender_t ( + id integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY NOT NULL, + name varchar(256) NOT NULL, + organization_id integer GENERATED ALWAYS AS (1) STORED NOT NULL +); + + + +comment on column gender_t.name is 'unique'; + + +CREATE TABLE group_t ( + id integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY NOT NULL, + external_id varchar(256), + name varchar(256) NOT NULL, + permissions varchar(256)[] CONSTRAINT enum_group_permissions CHECK (permissions <@ ARRAY['agenda_item.can_manage', 'agenda_item.can_see', 'agenda_item.can_see_internal', 'assignment.can_manage', 'assignment.can_manage_polls', 'assignment.can_nominate_other', 'assignment.can_nominate_self', 'assignment.can_see', 'chat.can_manage', 'list_of_speakers.can_be_speaker', 'list_of_speakers.can_manage', 'list_of_speakers.can_see', 'list_of_speakers.can_manage_moderator_notes', 'list_of_speakers.can_see_moderator_notes', 'mediafile.can_manage', 'mediafile.can_see', 'meeting.can_manage_logos_and_fonts', 'meeting.can_manage_settings', 'meeting.can_see_autopilot', 'meeting.can_see_frontpage', 'meeting.can_see_history', 'meeting.can_see_livestream', 'motion.can_create', 'motion.can_create_amendments', 'motion.can_forward', 'motion.can_manage', 'motion.can_manage_metadata', 'motion.can_manage_polls', 'motion.can_see', 'motion.can_see_internal', 'motion.can_see_origin', 'motion.can_support', 'poll.can_manage', 'poll.can_see_progress', 'projector.can_manage', 'projector.can_see', 'tag.can_manage', 'user.can_manage', 'user.can_manage_presence', 'user.can_see_sensitive_data', 'user.can_see', 'user.can_update', 'user.can_edit_own_delegation']::varchar[]), + weight integer, + used_as_motion_poll_default_id integer, + used_as_assignment_poll_default_id integer, + used_as_topic_poll_default_id integer, + used_as_poll_default_id integer, + meeting_id integer NOT NULL +); + + + +comment on column group_t.external_id is 'unique in meeting'; + + +CREATE TABLE history_entry_t ( + id integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY NOT NULL, + entries text[], + original_model_id varchar(256), + model_id varchar(100), + model_id_user_id integer GENERATED ALWAYS AS (CASE WHEN split_part(model_id, '/', 1) = 'user' THEN cast(split_part(model_id, '/', 2) AS INTEGER) ELSE null END) STORED, + model_id_motion_id integer GENERATED ALWAYS AS (CASE WHEN split_part(model_id, '/', 1) = 'motion' THEN cast(split_part(model_id, '/', 2) AS INTEGER) ELSE null END) STORED, + model_id_assignment_id integer GENERATED ALWAYS AS (CASE WHEN split_part(model_id, '/', 1) = 'assignment' THEN cast(split_part(model_id, '/', 2) AS INTEGER) ELSE null END) STORED, + CONSTRAINT valid_model_id_part1 CHECK (split_part(model_id, '/', 1) IN ('user','motion','assignment')), + position_id integer NOT NULL, + meeting_id integer +); + + + + +CREATE TABLE history_position_t ( + id integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY NOT NULL, + timestamp timestamptz, + original_user_id integer, + user_id integer +); + + + + +CREATE TABLE import_preview_t ( + id integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY NOT NULL, + name varchar(256) NOT NULL CONSTRAINT enum_import_preview_name CHECK (name IN ('account', 'participant', 'topic', 'committee', 'motion')), + state varchar(256) NOT NULL CONSTRAINT enum_import_preview_state CHECK (state IN ('warning', 'error', 'done')), + created timestamptz NOT NULL, + result jsonb +); + + + + +CREATE TABLE list_of_speakers_t ( + id integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY NOT NULL, + closed boolean DEFAULT False, + sequential_number integer NOT NULL, + CONSTRAINT unique_list_of_speakers_sequential_number UNIQUE (sequential_number, meeting_id), + moderator_notes text, + content_object_id varchar(100) NOT NULL, + content_object_id_motion_id integer UNIQUE GENERATED ALWAYS AS (CASE WHEN split_part(content_object_id, '/', 1) = 'motion' THEN cast(split_part(content_object_id, '/', 2) AS INTEGER) ELSE null END) STORED, + content_object_id_motion_block_id integer UNIQUE GENERATED ALWAYS AS (CASE WHEN split_part(content_object_id, '/', 1) = 'motion_block' THEN cast(split_part(content_object_id, '/', 2) AS INTEGER) ELSE null END) STORED, + content_object_id_assignment_id integer UNIQUE GENERATED ALWAYS AS (CASE WHEN split_part(content_object_id, '/', 1) = 'assignment' THEN cast(split_part(content_object_id, '/', 2) AS INTEGER) ELSE null END) STORED, + content_object_id_topic_id integer UNIQUE GENERATED ALWAYS AS (CASE WHEN split_part(content_object_id, '/', 1) = 'topic' THEN cast(split_part(content_object_id, '/', 2) AS INTEGER) ELSE null END) STORED, + content_object_id_meeting_mediafile_id integer UNIQUE GENERATED ALWAYS AS (CASE WHEN split_part(content_object_id, '/', 1) = 'meeting_mediafile' THEN cast(split_part(content_object_id, '/', 2) AS INTEGER) ELSE null END) STORED, + CONSTRAINT valid_content_object_id_part1 CHECK (split_part(content_object_id, '/', 1) IN ('motion','motion_block','assignment','topic','meeting_mediafile')), + meeting_id integer NOT NULL +); + + + +comment on column list_of_speakers_t.sequential_number is 'The (positive) serial number of this model in its meeting. This number is auto-generated and read-only.'; + + +CREATE TABLE mediafile_t ( + id integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY NOT NULL, + title varchar(256), + is_directory boolean, + filesize integer, + filename varchar(256), + mimetype varchar(256), + pdf_information jsonb, + create_timestamp timestamptz, + token varchar(256), + published_to_meetings_in_organization_id integer, + parent_id integer, + owner_id varchar(100) NOT NULL, + owner_id_meeting_id integer GENERATED ALWAYS AS (CASE WHEN split_part(owner_id, '/', 1) = 'meeting' THEN cast(split_part(owner_id, '/', 2) AS INTEGER) ELSE null END) STORED, + owner_id_organization_id integer GENERATED ALWAYS AS (CASE WHEN split_part(owner_id, '/', 1) = 'organization' THEN cast(split_part(owner_id, '/', 2) AS INTEGER) ELSE null END) STORED, + CONSTRAINT valid_owner_id_part1 CHECK (split_part(owner_id, '/', 1) IN ('meeting','organization')) +); + + + +comment on column mediafile_t.title is 'Title and parent_id must be unique.'; +comment on column mediafile_t.filesize is 'In bytes, not the human readable format anymore.'; +comment on column mediafile_t.filename is 'The uploaded filename. Will be used for downloading. Only writeable on create.'; + + +CREATE TABLE meeting_t ( + id integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY NOT NULL, + external_id varchar(256), + welcome_title varchar(256) DEFAULT 'Welcome to OpenSlides', + welcome_text text DEFAULT 'Space for your welcome text.', + name varchar(200) NOT NULL DEFAULT 'OpenSlides', + is_active_in_organization_id integer, + is_archived_in_organization_id integer, + description varchar(100), + location varchar(256), + start_time timestamptz, + end_time timestamptz, + locked_from_inside boolean, + imported_at timestamptz, + language varchar(256) CONSTRAINT enum_meeting_language CHECK (language IN ('en', 'de', 'it', 'es', 'ru', 'cs', 'fr')) DEFAULT 'en', + jitsi_domain varchar(256), + jitsi_room_name varchar(256), + jitsi_room_password varchar(256), + template_for_organization_id integer, + enable_anonymous boolean DEFAULT False, + custom_translations jsonb, + conference_show boolean DEFAULT False, + conference_auto_connect boolean DEFAULT False, + conference_los_restriction boolean DEFAULT True, + conference_stream_url varchar(256), + conference_stream_poster_url varchar(256), + conference_open_microphone boolean DEFAULT False, + conference_open_video boolean DEFAULT False, + conference_auto_connect_next_speakers integer DEFAULT 0, + conference_enable_helpdesk boolean DEFAULT False, + applause_enable boolean DEFAULT False, + applause_type varchar(256) CONSTRAINT enum_meeting_applause_type CHECK (applause_type IN ('applause-type-bar', 'applause-type-particles')) DEFAULT 'applause-type-bar', + applause_show_level boolean DEFAULT False, + applause_min_amount integer CONSTRAINT minimum_applause_min_amount CHECK (applause_min_amount >= 0) DEFAULT 1, + applause_max_amount integer CONSTRAINT minimum_applause_max_amount CHECK (applause_max_amount >= 0) DEFAULT 0, + applause_timeout integer CONSTRAINT minimum_applause_timeout CHECK (applause_timeout >= 0) DEFAULT 5, + applause_particle_image_url varchar(256), + projector_countdown_default_time integer NOT NULL DEFAULT 60, + projector_countdown_warning_time integer NOT NULL CONSTRAINT minimum_projector_countdown_warning_time CHECK (projector_countdown_warning_time >= 0) DEFAULT 0, + export_csv_encoding varchar(256) CONSTRAINT enum_meeting_export_csv_encoding CHECK (export_csv_encoding IN ('utf-8', 'iso-8859-15')) DEFAULT 'utf-8', + export_csv_separator varchar(256) DEFAULT ';', + export_pdf_pagenumber_alignment varchar(256) CONSTRAINT enum_meeting_export_pdf_pagenumber_alignment CHECK (export_pdf_pagenumber_alignment IN ('left', 'right', 'center')) DEFAULT 'center', + export_pdf_fontsize integer CONSTRAINT enum_meeting_export_pdf_fontsize CHECK (export_pdf_fontsize IN (10, 11, 12)) DEFAULT 10, + export_pdf_line_height double precision CONSTRAINT minimum_export_pdf_line_height CHECK (export_pdf_line_height >= 1.0) DEFAULT 1.25, + export_pdf_page_margin_left integer CONSTRAINT minimum_export_pdf_page_margin_left CHECK (export_pdf_page_margin_left >= 0) DEFAULT 20, + export_pdf_page_margin_top integer CONSTRAINT minimum_export_pdf_page_margin_top CHECK (export_pdf_page_margin_top >= 0) DEFAULT 25, + export_pdf_page_margin_right integer CONSTRAINT minimum_export_pdf_page_margin_right CHECK (export_pdf_page_margin_right >= 0) DEFAULT 20, + export_pdf_page_margin_bottom integer CONSTRAINT minimum_export_pdf_page_margin_bottom CHECK (export_pdf_page_margin_bottom >= 0) DEFAULT 20, + export_pdf_pagesize varchar(256) CONSTRAINT enum_meeting_export_pdf_pagesize CHECK (export_pdf_pagesize IN ('A4', 'A5')) DEFAULT 'A4', + agenda_show_subtitles boolean DEFAULT False, + agenda_enable_numbering boolean DEFAULT True, + agenda_number_prefix varchar(20), + agenda_numeral_system varchar(256) CONSTRAINT enum_meeting_agenda_numeral_system CHECK (agenda_numeral_system IN ('arabic', 'roman')) DEFAULT 'arabic', + agenda_item_creation varchar(256) CONSTRAINT enum_meeting_agenda_item_creation CHECK (agenda_item_creation IN ('always', 'never', 'default_yes', 'default_no')) DEFAULT 'default_no', + agenda_new_items_default_visibility varchar(256) CONSTRAINT enum_meeting_agenda_new_items_default_visibility CHECK (agenda_new_items_default_visibility IN ('common', 'internal', 'hidden')) DEFAULT 'internal', + agenda_show_internal_items_on_projector boolean DEFAULT False, + agenda_show_topic_navigation_on_detail_view boolean DEFAULT False, + list_of_speakers_amount_last_on_projector integer CONSTRAINT minimum_list_of_speakers_amount_last_on_projector CHECK (list_of_speakers_amount_last_on_projector >= -1) DEFAULT 0, + list_of_speakers_amount_next_on_projector integer CONSTRAINT minimum_list_of_speakers_amount_next_on_projector CHECK (list_of_speakers_amount_next_on_projector >= -1) DEFAULT -1, + list_of_speakers_couple_countdown boolean DEFAULT True, + list_of_speakers_show_amount_of_speakers_on_slide boolean DEFAULT True, + list_of_speakers_present_users_only boolean DEFAULT False, + list_of_speakers_show_first_contribution boolean DEFAULT False, + list_of_speakers_hide_contribution_count boolean DEFAULT False, + list_of_speakers_allow_multiple_speakers boolean DEFAULT False, + list_of_speakers_enable_point_of_order_speakers boolean DEFAULT True, + list_of_speakers_can_create_point_of_order_for_others boolean DEFAULT False, + list_of_speakers_enable_point_of_order_categories boolean DEFAULT False, + list_of_speakers_closing_disables_point_of_order boolean DEFAULT False, + list_of_speakers_enable_pro_contra_speech boolean DEFAULT False, + list_of_speakers_can_set_contribution_self boolean DEFAULT False, + list_of_speakers_speaker_note_for_everyone boolean DEFAULT True, + list_of_speakers_initially_closed boolean DEFAULT False, + list_of_speakers_default_structure_level_time integer CONSTRAINT minimum_list_of_speakers_default_structure_level_time CHECK (list_of_speakers_default_structure_level_time >= 0), + list_of_speakers_enable_interposed_question boolean, + list_of_speakers_intervention_time integer, + motions_default_workflow_id integer NOT NULL UNIQUE, + motions_default_amendment_workflow_id integer NOT NULL UNIQUE, + motions_preamble text DEFAULT 'The assembly may decide:', + motions_default_line_numbering varchar(256) CONSTRAINT enum_meeting_motions_default_line_numbering CHECK (motions_default_line_numbering IN ('outside', 'inline', 'none')) DEFAULT 'outside', + motions_line_length integer CONSTRAINT minimum_motions_line_length CHECK (motions_line_length >= 40) DEFAULT 85, + motions_reason_required boolean DEFAULT False, + motions_origin_motion_toggle_default boolean DEFAULT False, + motions_enable_origin_motion_display boolean DEFAULT False, + motions_enable_text_on_projector boolean DEFAULT True, + motions_enable_reason_on_projector boolean DEFAULT False, + motions_enable_sidebox_on_projector boolean DEFAULT False, + motions_enable_recommendation_on_projector boolean DEFAULT True, + motions_hide_metadata_background boolean DEFAULT False, + motions_show_referring_motions boolean DEFAULT True, + motions_show_sequential_number boolean DEFAULT True, + motions_create_enable_additional_submitter_text boolean, + motions_recommendations_by varchar(256), + motions_block_slide_columns integer CONSTRAINT minimum_motions_block_slide_columns CHECK (motions_block_slide_columns >= 1), + motions_recommendation_text_mode varchar(256) CONSTRAINT enum_meeting_motions_recommendation_text_mode CHECK (motions_recommendation_text_mode IN ('original', 'changed', 'diff', 'agreed')) DEFAULT 'diff', + motions_default_sorting varchar(256) CONSTRAINT enum_meeting_motions_default_sorting CHECK (motions_default_sorting IN ('number', 'weight')) DEFAULT 'number', + motions_number_type varchar(256) CONSTRAINT enum_meeting_motions_number_type CHECK (motions_number_type IN ('per_category', 'serially_numbered', 'manually')) DEFAULT 'per_category', + motions_number_min_digits integer DEFAULT 2, + motions_number_with_blank boolean DEFAULT False, + motions_amendments_enabled boolean DEFAULT True, + motions_amendments_in_main_list boolean DEFAULT True, + motions_amendments_of_amendments boolean DEFAULT False, + motions_amendments_prefix varchar(256) DEFAULT '-Ä', + motions_amendments_text_mode varchar(256) CONSTRAINT enum_meeting_motions_amendments_text_mode CHECK (motions_amendments_text_mode IN ('freestyle', 'fulltext', 'paragraph')) DEFAULT 'paragraph', + motions_amendments_multiple_paragraphs boolean DEFAULT True, + motions_supporters_min_amount integer CONSTRAINT minimum_motions_supporters_min_amount CHECK (motions_supporters_min_amount >= 0) DEFAULT 0, + motions_enable_editor boolean, + motions_enable_working_group_speaker boolean, + motions_export_title varchar(256) DEFAULT 'Motions', + motions_export_preamble text, + motions_export_submitter_recommendation boolean DEFAULT True, + motions_export_follow_recommendation boolean DEFAULT False, + motions_enable_restricted_editor_for_manager boolean, + motions_enable_restricted_editor_for_non_manager boolean, + motion_poll_ballot_paper_selection varchar(256) CONSTRAINT enum_meeting_motion_poll_ballot_paper_selection CHECK (motion_poll_ballot_paper_selection IN ('NUMBER_OF_DELEGATES', 'NUMBER_OF_ALL_PARTICIPANTS', 'CUSTOM_NUMBER')) DEFAULT 'CUSTOM_NUMBER', + motion_poll_ballot_paper_number integer DEFAULT 8, + motion_poll_default_type varchar(256) DEFAULT 'pseudoanonymous', + motion_poll_default_method varchar(256) DEFAULT 'YNA', + motion_poll_default_onehundred_percent_base varchar(256) CONSTRAINT enum_meeting_motion_poll_default_onehundred_percent_base CHECK (motion_poll_default_onehundred_percent_base IN ('Y', 'YN', 'YNA', 'N', 'valid', 'cast', 'entitled', 'entitled_present', 'disabled')) DEFAULT 'YNA', + motion_poll_default_backend varchar(256) CONSTRAINT enum_meeting_motion_poll_default_backend CHECK (motion_poll_default_backend IN ('long', 'fast')) DEFAULT 'fast', + motion_poll_projection_name_order_first varchar(256) NOT NULL CONSTRAINT enum_meeting_motion_poll_projection_name_order_first CHECK (motion_poll_projection_name_order_first IN ('first_name', 'last_name')) DEFAULT 'last_name', + motion_poll_projection_max_columns integer NOT NULL DEFAULT 6, + users_enable_presence_view boolean DEFAULT False, + users_enable_vote_weight boolean DEFAULT False, + users_allow_self_set_present boolean DEFAULT True, + users_pdf_welcometitle varchar(256) DEFAULT 'Welcome to OpenSlides', + users_pdf_welcometext text DEFAULT '[Place for your welcome and help text.]', + users_pdf_wlan_ssid varchar(256), + users_pdf_wlan_password varchar(256), + users_pdf_wlan_encryption varchar(256) CONSTRAINT enum_meeting_users_pdf_wlan_encryption CHECK (users_pdf_wlan_encryption IN ('', 'WEP', 'WPA', 'nopass')) DEFAULT 'WPA', + users_email_sender varchar(256) DEFAULT 'OpenSlides', + users_email_replyto varchar(256), + users_email_subject varchar(256) DEFAULT 'OpenSlides access data', + users_email_body text DEFAULT 'Dear {name}, + +this is your personal OpenSlides login: + +{url} +Username: {username} +Password: {password} + + +This email was generated automatically.', + users_enable_vote_delegations boolean, + users_forbid_delegator_in_list_of_speakers boolean, + users_forbid_delegator_as_submitter boolean, + users_forbid_delegator_as_supporter boolean, + users_forbid_delegator_to_vote boolean, + assignments_export_title varchar(256) DEFAULT 'Elections', + assignments_export_preamble text, + assignment_poll_ballot_paper_selection varchar(256) CONSTRAINT enum_meeting_assignment_poll_ballot_paper_selection CHECK (assignment_poll_ballot_paper_selection IN ('NUMBER_OF_DELEGATES', 'NUMBER_OF_ALL_PARTICIPANTS', 'CUSTOM_NUMBER')) DEFAULT 'CUSTOM_NUMBER', + assignment_poll_ballot_paper_number integer DEFAULT 8, + assignment_poll_add_candidates_to_list_of_speakers boolean DEFAULT False, + assignment_poll_enable_max_votes_per_option boolean DEFAULT False, + assignment_poll_sort_poll_result_by_votes boolean DEFAULT True, + assignment_poll_default_type varchar(256) DEFAULT 'pseudoanonymous', + assignment_poll_default_method varchar(256) DEFAULT 'Y', + assignment_poll_default_onehundred_percent_base varchar(256) CONSTRAINT enum_meeting_assignment_poll_default_onehundred_percent_base CHECK (assignment_poll_default_onehundred_percent_base IN ('Y', 'YN', 'YNA', 'N', 'valid', 'cast', 'entitled', 'entitled_present', 'disabled')) DEFAULT 'valid', + assignment_poll_default_backend varchar(256) CONSTRAINT enum_meeting_assignment_poll_default_backend CHECK (assignment_poll_default_backend IN ('long', 'fast')) DEFAULT 'fast', + poll_ballot_paper_selection varchar(256) CONSTRAINT enum_meeting_poll_ballot_paper_selection CHECK (poll_ballot_paper_selection IN ('NUMBER_OF_DELEGATES', 'NUMBER_OF_ALL_PARTICIPANTS', 'CUSTOM_NUMBER')), + poll_ballot_paper_number integer, + poll_sort_poll_result_by_votes boolean, + poll_default_type varchar(256) DEFAULT 'analog', + poll_default_method varchar(256), + poll_default_onehundred_percent_base varchar(256) CONSTRAINT enum_meeting_poll_default_onehundred_percent_base CHECK (poll_default_onehundred_percent_base IN ('Y', 'YN', 'YNA', 'N', 'valid', 'cast', 'entitled', 'entitled_present', 'disabled')) DEFAULT 'YNA', + poll_default_backend varchar(256) CONSTRAINT enum_meeting_poll_default_backend CHECK (poll_default_backend IN ('long', 'fast')) DEFAULT 'fast', + poll_default_live_voting_enabled boolean DEFAULT False, + poll_couple_countdown boolean DEFAULT True, + logo_projector_main_id integer UNIQUE, + logo_projector_header_id integer UNIQUE, + logo_web_header_id integer UNIQUE, + logo_pdf_header_l_id integer UNIQUE, + logo_pdf_header_r_id integer UNIQUE, + logo_pdf_footer_l_id integer UNIQUE, + logo_pdf_footer_r_id integer UNIQUE, + logo_pdf_ballot_paper_id integer UNIQUE, + font_regular_id integer UNIQUE, + font_italic_id integer UNIQUE, + font_bold_id integer UNIQUE, + font_bold_italic_id integer UNIQUE, + font_monospace_id integer UNIQUE, + font_chyron_speaker_name_id integer UNIQUE, + font_projector_h1_id integer UNIQUE, + font_projector_h2_id integer UNIQUE, + committee_id integer NOT NULL, + reference_projector_id integer NOT NULL UNIQUE, + list_of_speakers_countdown_id integer UNIQUE, + poll_countdown_id integer UNIQUE, + default_group_id integer NOT NULL UNIQUE, + admin_group_id integer UNIQUE, + anonymous_group_id integer UNIQUE +); + + + +comment on column meeting_t.external_id is 'unique in committee'; +comment on column meeting_t.is_active_in_organization_id is 'Backrelation and boolean flag at once'; +comment on column meeting_t.is_archived_in_organization_id is 'Backrelation and boolean flag at once'; +comment on column meeting_t.list_of_speakers_default_structure_level_time is '0 disables structure level countdowns.'; +comment on column meeting_t.list_of_speakers_intervention_time is '0 disables intervention speakers.'; +comment on column meeting_t.poll_default_live_voting_enabled is 'Defines default ''poll.live_voting_enabled'' option suggested to user. Is not used in the validations.'; + + +CREATE TABLE meeting_mediafile_t ( + id integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY NOT NULL, + mediafile_id integer NOT NULL, + meeting_id integer NOT NULL, + is_public boolean NOT NULL +); + + + +comment on column meeting_mediafile_t.is_public is 'Calculated in actions. Used to discern whether the (meeting-)mediafile can be seen by everyone, because, in the case of inherited_access_group_ids == [], it would otherwise not be clear. inherited_access_group_ids == [] can have two causes: cancelling access groups (=> is_public := false) or no access groups at all (=> is_public := true)'; + + +CREATE TABLE meeting_user_t ( + id integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY NOT NULL, + comment text, + number varchar(256), + about_me text, + vote_weight decimal(16,6) CONSTRAINT minimum_vote_weight CHECK (vote_weight >= 0.000001), + locked_out boolean, + user_id integer NOT NULL, + meeting_id integer NOT NULL, + vote_delegated_to_id integer +); + + + + +CREATE TABLE motion_t ( + id integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY NOT NULL, + number varchar(256), + number_value integer, + sequential_number integer NOT NULL, + CONSTRAINT unique_motion_sequential_number UNIQUE (sequential_number, meeting_id), + title varchar(256) NOT NULL, + text text, + text_hash varchar(256), + amendment_paragraphs jsonb, + modified_final_version text, + reason text, + category_weight integer DEFAULT 10000, + state_extension varchar(256), + recommendation_extension varchar(256), + sort_weight integer DEFAULT 10000, + created timestamptz, + last_modified timestamptz, + workflow_timestamp timestamptz, + start_line_number integer CONSTRAINT minimum_start_line_number CHECK (start_line_number >= 1) DEFAULT 1, + forwarded timestamptz, + additional_submitter varchar(256), + marked_forwarded boolean, + lead_motion_id integer, + sort_parent_id integer, + origin_id integer, + origin_meeting_id integer, + state_id integer NOT NULL, + recommendation_id integer, + category_id integer, + block_id integer, + meeting_id integer NOT NULL +); + + + +comment on column motion_t.number_value is 'The number value of this motion. This number is auto-generated and read-only.'; +comment on column motion_t.sequential_number is 'The (positive) serial number of this model in its meeting. This number is auto-generated and read-only.'; +comment on column motion_t.marked_forwarded is 'Forwarded amendments can be marked as such. This is just optional, however. Forwarded amendments can also have this field set to false.'; + + +CREATE TABLE motion_block_t ( + id integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY NOT NULL, + title varchar(256) NOT NULL, + internal boolean, + sequential_number integer NOT NULL, + CONSTRAINT unique_motion_block_sequential_number UNIQUE (sequential_number, meeting_id), + meeting_id integer NOT NULL +); + + + +comment on column motion_block_t.sequential_number is 'The (positive) serial number of this model in its meeting. This number is auto-generated and read-only.'; + + +CREATE TABLE motion_category_t ( + id integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY NOT NULL, + name varchar(256) NOT NULL, + prefix varchar(256), + weight integer DEFAULT 10000, + level integer, + sequential_number integer NOT NULL, + CONSTRAINT unique_motion_category_sequential_number UNIQUE (sequential_number, meeting_id), + parent_id integer, + meeting_id integer NOT NULL +); + + + +comment on column motion_category_t.level is 'Calculated field.'; +comment on column motion_category_t.sequential_number is 'The (positive) serial number of this model in its meeting. This number is auto-generated and read-only.'; + + +CREATE TABLE motion_change_recommendation_t ( + id integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY NOT NULL, + rejected boolean DEFAULT False, + internal boolean DEFAULT False, + type varchar(256) CONSTRAINT enum_motion_change_recommendation_type CHECK (type IN ('replacement', 'insertion', 'deletion', 'other')) DEFAULT 'replacement', + other_description varchar(256), + line_from integer CONSTRAINT minimum_line_from CHECK (line_from >= 0), + line_to integer CONSTRAINT minimum_line_to CHECK (line_to >= 0), + text text, + creation_time timestamptz, + motion_id integer NOT NULL, + meeting_id integer NOT NULL +); + + + + +CREATE TABLE motion_comment_t ( + id integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY NOT NULL, + comment text, + motion_id integer NOT NULL, + section_id integer NOT NULL, + meeting_id integer NOT NULL +); + + + + +CREATE TABLE motion_comment_section_t ( + id integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY NOT NULL, + name varchar(256) NOT NULL, + weight integer DEFAULT 10000, + sequential_number integer NOT NULL, + CONSTRAINT unique_motion_comment_section_sequential_number UNIQUE (sequential_number, meeting_id), + submitter_can_write boolean, + meeting_id integer NOT NULL +); + + + +comment on column motion_comment_section_t.sequential_number is 'The (positive) serial number of this model in its meeting. This number is auto-generated and read-only.'; + + +CREATE TABLE motion_editor_t ( + id integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY NOT NULL, + weight integer, + meeting_user_id integer, + motion_id integer NOT NULL, + meeting_id integer NOT NULL +); + + + + +CREATE TABLE motion_state_t ( + id integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY NOT NULL, + name varchar(256) NOT NULL, + weight integer NOT NULL, + recommendation_label varchar(256), + is_internal boolean, + css_class varchar(256) NOT NULL CONSTRAINT enum_motion_state_css_class CHECK (css_class IN ('grey', 'red', 'green', 'lightblue', 'yellow')) DEFAULT 'lightblue', + restrictions varchar(256)[] CONSTRAINT enum_motion_state_restrictions CHECK (restrictions <@ ARRAY['motion.can_see_internal', 'motion.can_manage_metadata', 'motion.can_manage', 'is_submitter']::varchar[]) DEFAULT '{}', + allow_support boolean DEFAULT False, + allow_create_poll boolean DEFAULT False, + allow_submitter_edit boolean DEFAULT False, + set_number boolean DEFAULT True, + show_state_extension_field boolean DEFAULT False, + show_recommendation_extension_field boolean DEFAULT False, + merge_amendment_into_final varchar(256) CONSTRAINT enum_motion_state_merge_amendment_into_final CHECK (merge_amendment_into_final IN ('do_not_merge', 'undefined', 'do_merge')) DEFAULT 'undefined', + allow_motion_forwarding boolean DEFAULT False, + allow_amendment_forwarding boolean, + set_workflow_timestamp boolean DEFAULT False, + state_button_label varchar(256), + submitter_withdraw_state_id integer, + workflow_id integer NOT NULL, + meeting_id integer NOT NULL +); + + + + +CREATE TABLE motion_submitter_t ( + id integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY NOT NULL, + weight integer, + meeting_user_id integer, + motion_id integer NOT NULL, + meeting_id integer NOT NULL +); + + + + +CREATE TABLE motion_supporter_t ( + id integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY NOT NULL, + meeting_user_id integer, + motion_id integer NOT NULL, + meeting_id integer NOT NULL +); + + + + +CREATE TABLE motion_workflow_t ( + id integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY NOT NULL, + name varchar(256) NOT NULL, + sequential_number integer NOT NULL, + CONSTRAINT unique_motion_workflow_sequential_number UNIQUE (sequential_number, meeting_id), + first_state_id integer NOT NULL UNIQUE, + meeting_id integer NOT NULL +); + + + +comment on column motion_workflow_t.sequential_number is 'The (positive) serial number of this model in its meeting. This number is auto-generated and read-only.'; + + +CREATE TABLE motion_working_group_speaker_t ( + id integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY NOT NULL, + weight integer, + meeting_user_id integer, + motion_id integer NOT NULL, + meeting_id integer NOT NULL +); + + + + +CREATE TABLE option_t ( + id integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY NOT NULL, + weight integer DEFAULT 10000, + text text, + yes decimal(16,6), + no decimal(16,6), + abstain decimal(16,6), + poll_id integer, + used_as_global_option_in_poll_id integer UNIQUE, + content_object_id varchar(100), + content_object_id_motion_id integer GENERATED ALWAYS AS (CASE WHEN split_part(content_object_id, '/', 1) = 'motion' THEN cast(split_part(content_object_id, '/', 2) AS INTEGER) ELSE null END) STORED, + content_object_id_user_id integer GENERATED ALWAYS AS (CASE WHEN split_part(content_object_id, '/', 1) = 'user' THEN cast(split_part(content_object_id, '/', 2) AS INTEGER) ELSE null END) STORED, + content_object_id_poll_candidate_list_id integer UNIQUE GENERATED ALWAYS AS (CASE WHEN split_part(content_object_id, '/', 1) = 'poll_candidate_list' THEN cast(split_part(content_object_id, '/', 2) AS INTEGER) ELSE null END) STORED, + CONSTRAINT valid_content_object_id_part1 CHECK (split_part(content_object_id, '/', 1) IN ('motion','user','poll_candidate_list')), + meeting_id integer NOT NULL +); + + + + +CREATE TABLE organization_t ( + id integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY NOT NULL, + name varchar(256), + description text, + legal_notice text, + privacy_policy text, + login_text text, + reset_password_verbose_errors boolean, + disable_forward_with_attachments boolean, + restrict_edit_forward_committees boolean, + enable_electronic_voting boolean, + enable_chat boolean, + limit_of_meetings integer CONSTRAINT minimum_limit_of_meetings CHECK (limit_of_meetings >= 0) DEFAULT 0, + limit_of_users integer CONSTRAINT minimum_limit_of_users CHECK (limit_of_users >= 0) DEFAULT 0, + default_language varchar(256) CONSTRAINT enum_organization_default_language CHECK (default_language IN ('en', 'de', 'it', 'es', 'ru', 'cs', 'fr')) DEFAULT 'en', + require_duplicate_from boolean, + enable_anonymous boolean, + restrict_editing_same_level_committee_admins boolean, + saml_enabled boolean, + saml_login_button_text varchar(256) DEFAULT 'SAML login', + saml_attr_mapping jsonb, + saml_metadata_idp text, + saml_metadata_sp text, + saml_private_key text, + theme_id integer NOT NULL UNIQUE, + users_email_sender varchar(256) DEFAULT 'OpenSlides', + users_email_replyto varchar(256), + users_email_subject varchar(256) DEFAULT 'OpenSlides access data', + users_email_body text DEFAULT 'Dear {name}, + +this is your personal OpenSlides login: + +{url} +Username: {username} +Password: {password} + + +This email was generated automatically.', + url varchar(256) DEFAULT 'https://example.com' +); + + + +comment on column organization_t.limit_of_meetings is 'Maximum of active meetings for the whole organization. 0 means no limitation at all'; +comment on column organization_t.limit_of_users is 'Maximum of active users for the whole organization. 0 means no limitation at all'; + + +CREATE TABLE organization_tag_t ( + id integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY NOT NULL, + name varchar(256) NOT NULL, + color varchar(7) CHECK (color is null or color ~* '^#[a-f0-9]{6}$') NOT NULL, + organization_id integer GENERATED ALWAYS AS (1) STORED NOT NULL +); + + + + +CREATE TABLE personal_note_t ( + id integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY NOT NULL, + note text, + star boolean, + meeting_user_id integer NOT NULL, + content_object_id varchar(100), + content_object_id_motion_id integer GENERATED ALWAYS AS (CASE WHEN split_part(content_object_id, '/', 1) = 'motion' THEN cast(split_part(content_object_id, '/', 2) AS INTEGER) ELSE null END) STORED, + CONSTRAINT valid_content_object_id_part1 CHECK (split_part(content_object_id, '/', 1) IN ('motion')), + meeting_id integer NOT NULL +); + + + + +CREATE TABLE point_of_order_category_t ( + id integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY NOT NULL, + text varchar(256) NOT NULL, + rank integer NOT NULL, + meeting_id integer NOT NULL +); + + + + +CREATE TABLE poll_t ( + id integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY NOT NULL, + title varchar(256) NOT NULL, + description varchar(256), + type varchar(256) NOT NULL CONSTRAINT enum_poll_type CHECK (type IN ('analog', 'named', 'pseudoanonymous', 'cryptographic')), + backend varchar(256) NOT NULL CONSTRAINT enum_poll_backend CHECK (backend IN ('long', 'fast')) DEFAULT 'fast', + is_pseudoanonymized boolean, + pollmethod varchar(256) NOT NULL CONSTRAINT enum_poll_pollmethod CHECK (pollmethod IN ('Y', 'YN', 'YNA', 'N')), + state varchar(256) CONSTRAINT enum_poll_state CHECK (state IN ('created', 'started', 'finished', 'published')) DEFAULT 'created', + min_votes_amount integer CONSTRAINT minimum_min_votes_amount CHECK (min_votes_amount >= 1) DEFAULT 1, + max_votes_amount integer CONSTRAINT minimum_max_votes_amount CHECK (max_votes_amount >= 1) DEFAULT 1, + max_votes_per_option integer CONSTRAINT minimum_max_votes_per_option CHECK (max_votes_per_option >= 1) DEFAULT 1, + global_yes boolean DEFAULT False, + global_no boolean DEFAULT False, + global_abstain boolean DEFAULT False, + onehundred_percent_base varchar(256) NOT NULL CONSTRAINT enum_poll_onehundred_percent_base CHECK (onehundred_percent_base IN ('Y', 'YN', 'YNA', 'N', 'valid', 'cast', 'entitled', 'entitled_present', 'disabled')) DEFAULT 'disabled', + votesvalid decimal(16,6), + votesinvalid decimal(16,6), + votescast decimal(16,6), + entitled_users_at_stop jsonb, + live_voting_enabled boolean DEFAULT False, + sequential_number integer NOT NULL, + CONSTRAINT unique_poll_sequential_number UNIQUE (sequential_number, meeting_id), + content_object_id varchar(100) NOT NULL, + content_object_id_motion_id integer GENERATED ALWAYS AS (CASE WHEN split_part(content_object_id, '/', 1) = 'motion' THEN cast(split_part(content_object_id, '/', 2) AS INTEGER) ELSE null END) STORED, + content_object_id_assignment_id integer GENERATED ALWAYS AS (CASE WHEN split_part(content_object_id, '/', 1) = 'assignment' THEN cast(split_part(content_object_id, '/', 2) AS INTEGER) ELSE null END) STORED, + content_object_id_topic_id integer GENERATED ALWAYS AS (CASE WHEN split_part(content_object_id, '/', 1) = 'topic' THEN cast(split_part(content_object_id, '/', 2) AS INTEGER) ELSE null END) STORED, + CONSTRAINT valid_content_object_id_part1 CHECK (split_part(content_object_id, '/', 1) IN ('motion','assignment','topic')), + global_option_id integer UNIQUE, + meeting_id integer NOT NULL +); + + + +comment on column poll_t.live_voting_enabled is 'If true, the vote service sends the votes of the users to the autoupdate service.'; +comment on column poll_t.sequential_number is 'The (positive) serial number of this model in its meeting. This number is auto-generated and read-only.'; + +/* + Fields without SQL definition for table poll + + poll/live_votes: type:JSON is marked as a calculated field and not generated in schema + +*/ + +CREATE TABLE poll_candidate_t ( + id integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY NOT NULL, + poll_candidate_list_id integer NOT NULL, + user_id integer, + weight integer NOT NULL, + meeting_id integer NOT NULL +); + + + + +CREATE TABLE poll_candidate_list_t ( + id integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY NOT NULL, + meeting_id integer NOT NULL +); + + + + +CREATE TABLE projection_t ( + id integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY NOT NULL, + options jsonb, + stable boolean DEFAULT False, + weight integer, + type varchar(256), + current_projector_id integer, + preview_projector_id integer, + history_projector_id integer, + content_object_id varchar(100) NOT NULL, + content_object_id_meeting_id integer GENERATED ALWAYS AS (CASE WHEN split_part(content_object_id, '/', 1) = 'meeting' THEN cast(split_part(content_object_id, '/', 2) AS INTEGER) ELSE null END) STORED, + content_object_id_motion_id integer GENERATED ALWAYS AS (CASE WHEN split_part(content_object_id, '/', 1) = 'motion' THEN cast(split_part(content_object_id, '/', 2) AS INTEGER) ELSE null END) STORED, + content_object_id_meeting_mediafile_id integer GENERATED ALWAYS AS (CASE WHEN split_part(content_object_id, '/', 1) = 'meeting_mediafile' THEN cast(split_part(content_object_id, '/', 2) AS INTEGER) ELSE null END) STORED, + content_object_id_list_of_speakers_id integer GENERATED ALWAYS AS (CASE WHEN split_part(content_object_id, '/', 1) = 'list_of_speakers' THEN cast(split_part(content_object_id, '/', 2) AS INTEGER) ELSE null END) STORED, + content_object_id_motion_block_id integer GENERATED ALWAYS AS (CASE WHEN split_part(content_object_id, '/', 1) = 'motion_block' THEN cast(split_part(content_object_id, '/', 2) AS INTEGER) ELSE null END) STORED, + content_object_id_assignment_id integer GENERATED ALWAYS AS (CASE WHEN split_part(content_object_id, '/', 1) = 'assignment' THEN cast(split_part(content_object_id, '/', 2) AS INTEGER) ELSE null END) STORED, + content_object_id_agenda_item_id integer GENERATED ALWAYS AS (CASE WHEN split_part(content_object_id, '/', 1) = 'agenda_item' THEN cast(split_part(content_object_id, '/', 2) AS INTEGER) ELSE null END) STORED, + content_object_id_topic_id integer GENERATED ALWAYS AS (CASE WHEN split_part(content_object_id, '/', 1) = 'topic' THEN cast(split_part(content_object_id, '/', 2) AS INTEGER) ELSE null END) STORED, + content_object_id_poll_id integer GENERATED ALWAYS AS (CASE WHEN split_part(content_object_id, '/', 1) = 'poll' THEN cast(split_part(content_object_id, '/', 2) AS INTEGER) ELSE null END) STORED, + content_object_id_projector_message_id integer GENERATED ALWAYS AS (CASE WHEN split_part(content_object_id, '/', 1) = 'projector_message' THEN cast(split_part(content_object_id, '/', 2) AS INTEGER) ELSE null END) STORED, + content_object_id_projector_countdown_id integer GENERATED ALWAYS AS (CASE WHEN split_part(content_object_id, '/', 1) = 'projector_countdown' THEN cast(split_part(content_object_id, '/', 2) AS INTEGER) ELSE null END) STORED, + CONSTRAINT valid_content_object_id_part1 CHECK (split_part(content_object_id, '/', 1) IN ('meeting','motion','meeting_mediafile','list_of_speakers','motion_block','assignment','agenda_item','topic','poll','projector_message','projector_countdown')), + meeting_id integer NOT NULL +); + + + +/* + Fields without SQL definition for table projection + + projection/content: type:JSON is marked as a calculated field and not generated in schema + +*/ + +CREATE TABLE projector_t ( + id integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY NOT NULL, + name varchar(256), + is_internal boolean DEFAULT False, + scale integer DEFAULT 0, + scroll integer CONSTRAINT minimum_scroll CHECK (scroll >= 0) DEFAULT 0, + width integer CONSTRAINT minimum_width CHECK (width >= 1) DEFAULT 1200, + aspect_ratio_numerator integer CONSTRAINT minimum_aspect_ratio_numerator CHECK (aspect_ratio_numerator >= 1) DEFAULT 16, + aspect_ratio_denominator integer CONSTRAINT minimum_aspect_ratio_denominator CHECK (aspect_ratio_denominator >= 1) DEFAULT 9, + color varchar(7) CHECK (color is null or color ~* '^#[a-f0-9]{6}$') DEFAULT '#000000', + background_color varchar(7) CHECK (background_color is null or background_color ~* '^#[a-f0-9]{6}$') DEFAULT '#ffffff', + header_background_color varchar(7) CHECK (header_background_color is null or header_background_color ~* '^#[a-f0-9]{6}$') DEFAULT '#317796', + header_font_color varchar(7) CHECK (header_font_color is null or header_font_color ~* '^#[a-f0-9]{6}$') DEFAULT '#f5f5f5', + header_h1_color varchar(7) CHECK (header_h1_color is null or header_h1_color ~* '^#[a-f0-9]{6}$') DEFAULT '#317796', + chyron_background_color varchar(7) CHECK (chyron_background_color is null or chyron_background_color ~* '^#[a-f0-9]{6}$') DEFAULT '#317796', + chyron_background_color_2 varchar(7) CHECK (chyron_background_color_2 is null or chyron_background_color_2 ~* '^#[a-f0-9]{6}$') DEFAULT '#134768', + chyron_font_color varchar(7) CHECK (chyron_font_color is null or chyron_font_color ~* '^#[a-f0-9]{6}$') DEFAULT '#ffffff', + chyron_font_color_2 varchar(7) CHECK (chyron_font_color_2 is null or chyron_font_color_2 ~* '^#[a-f0-9]{6}$') DEFAULT '#ffffff', + show_header_footer boolean DEFAULT True, + show_title boolean DEFAULT True, + show_logo boolean DEFAULT True, + show_clock boolean DEFAULT True, + sequential_number integer NOT NULL, + CONSTRAINT unique_projector_sequential_number UNIQUE (sequential_number, meeting_id), + used_as_default_projector_for_agenda_item_list_in_meeting_id integer, + used_as_default_projector_for_topic_in_meeting_id integer, + used_as_default_projector_for_list_of_speakers_in_meeting_id integer, + used_as_default_projector_for_current_los_in_meeting_id integer, + used_as_default_projector_for_motion_in_meeting_id integer, + used_as_default_projector_for_amendment_in_meeting_id integer, + used_as_default_projector_for_motion_block_in_meeting_id integer, + used_as_default_projector_for_assignment_in_meeting_id integer, + used_as_default_projector_for_mediafile_in_meeting_id integer, + used_as_default_projector_for_message_in_meeting_id integer, + used_as_default_projector_for_countdown_in_meeting_id integer, + used_as_default_projector_for_assignment_poll_in_meeting_id integer, + used_as_default_projector_for_motion_poll_in_meeting_id integer, + used_as_default_projector_for_poll_in_meeting_id integer, + meeting_id integer NOT NULL +); + + + +comment on column projector_t.sequential_number is 'The (positive) serial number of this model in its meeting. This number is auto-generated and read-only.'; + + +CREATE TABLE projector_countdown_t ( + id integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY NOT NULL, + title varchar(256) NOT NULL, + description varchar(256) DEFAULT '', + default_time integer, + countdown_time double precision DEFAULT 60, + running boolean DEFAULT False, + meeting_id integer NOT NULL +); + + + + +CREATE TABLE projector_message_t ( + id integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY NOT NULL, + message text, + meeting_id integer NOT NULL +); + + + + +CREATE TABLE speaker_t ( + id integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY NOT NULL, + begin_time timestamptz, + end_time timestamptz, + pause_time timestamptz, + unpause_time timestamptz, + total_pause integer, + weight integer DEFAULT 10000, + speech_state varchar(256) CONSTRAINT enum_speaker_speech_state CHECK (speech_state IN ('contribution', 'pro', 'contra', 'intervention', 'interposed_question')), + answer boolean, + note varchar(250), + point_of_order boolean, + list_of_speakers_id integer NOT NULL, + structure_level_list_of_speakers_id integer, + meeting_user_id integer, + point_of_order_category_id integer, + meeting_id integer NOT NULL +); + + + + +CREATE TABLE structure_level_t ( + id integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY NOT NULL, + name varchar(256) NOT NULL, + color varchar(7) CHECK (color is null or color ~* '^#[a-f0-9]{6}$'), + default_time integer CONSTRAINT minimum_default_time CHECK (default_time >= 0), + meeting_id integer NOT NULL +); + + + + +CREATE TABLE structure_level_list_of_speakers_t ( + id integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY NOT NULL, + structure_level_id integer NOT NULL, + list_of_speakers_id integer NOT NULL, + initial_time integer NOT NULL CONSTRAINT minimum_initial_time CHECK (initial_time >= 1), + additional_time double precision, + remaining_time double precision NOT NULL, + current_start_time timestamptz, + meeting_id integer NOT NULL +); + + + +comment on column structure_level_list_of_speakers_t.initial_time is 'The initial time of this structure_level for this LoS'; +comment on column structure_level_list_of_speakers_t.additional_time is 'The summed added time of this structure_level for this LoS'; +comment on column structure_level_list_of_speakers_t.remaining_time is 'The currently remaining time of this structure_level for this LoS'; +comment on column structure_level_list_of_speakers_t.current_start_time is 'The current start time of a speaker for this structure_level. Is only set if a currently speaking speaker exists'; + + +CREATE TABLE tag_t ( + id integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY NOT NULL, + name varchar(256) NOT NULL, + meeting_id integer NOT NULL +); + + + + +CREATE TABLE theme_t ( + id integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY NOT NULL, + name varchar(256) NOT NULL, + accent_100 varchar(7) CHECK (accent_100 is null or accent_100 ~* '^#[a-f0-9]{6}$'), + accent_200 varchar(7) CHECK (accent_200 is null or accent_200 ~* '^#[a-f0-9]{6}$'), + accent_300 varchar(7) CHECK (accent_300 is null or accent_300 ~* '^#[a-f0-9]{6}$'), + accent_400 varchar(7) CHECK (accent_400 is null or accent_400 ~* '^#[a-f0-9]{6}$'), + accent_50 varchar(7) CHECK (accent_50 is null or accent_50 ~* '^#[a-f0-9]{6}$'), + accent_500 varchar(7) CHECK (accent_500 is null or accent_500 ~* '^#[a-f0-9]{6}$') DEFAULT '#2196f3', + accent_600 varchar(7) CHECK (accent_600 is null or accent_600 ~* '^#[a-f0-9]{6}$'), + accent_700 varchar(7) CHECK (accent_700 is null or accent_700 ~* '^#[a-f0-9]{6}$'), + accent_800 varchar(7) CHECK (accent_800 is null or accent_800 ~* '^#[a-f0-9]{6}$'), + accent_900 varchar(7) CHECK (accent_900 is null or accent_900 ~* '^#[a-f0-9]{6}$'), + accent_a100 varchar(7) CHECK (accent_a100 is null or accent_a100 ~* '^#[a-f0-9]{6}$'), + accent_a200 varchar(7) CHECK (accent_a200 is null or accent_a200 ~* '^#[a-f0-9]{6}$'), + accent_a400 varchar(7) CHECK (accent_a400 is null or accent_a400 ~* '^#[a-f0-9]{6}$'), + accent_a700 varchar(7) CHECK (accent_a700 is null or accent_a700 ~* '^#[a-f0-9]{6}$'), + primary_100 varchar(7) CHECK (primary_100 is null or primary_100 ~* '^#[a-f0-9]{6}$'), + primary_200 varchar(7) CHECK (primary_200 is null or primary_200 ~* '^#[a-f0-9]{6}$'), + primary_300 varchar(7) CHECK (primary_300 is null or primary_300 ~* '^#[a-f0-9]{6}$'), + primary_400 varchar(7) CHECK (primary_400 is null or primary_400 ~* '^#[a-f0-9]{6}$'), + primary_50 varchar(7) CHECK (primary_50 is null or primary_50 ~* '^#[a-f0-9]{6}$'), + primary_500 varchar(7) CHECK (primary_500 is null or primary_500 ~* '^#[a-f0-9]{6}$') DEFAULT '#317796', + primary_600 varchar(7) CHECK (primary_600 is null or primary_600 ~* '^#[a-f0-9]{6}$'), + primary_700 varchar(7) CHECK (primary_700 is null or primary_700 ~* '^#[a-f0-9]{6}$'), + primary_800 varchar(7) CHECK (primary_800 is null or primary_800 ~* '^#[a-f0-9]{6}$'), + primary_900 varchar(7) CHECK (primary_900 is null or primary_900 ~* '^#[a-f0-9]{6}$'), + primary_a100 varchar(7) CHECK (primary_a100 is null or primary_a100 ~* '^#[a-f0-9]{6}$'), + primary_a200 varchar(7) CHECK (primary_a200 is null or primary_a200 ~* '^#[a-f0-9]{6}$'), + primary_a400 varchar(7) CHECK (primary_a400 is null or primary_a400 ~* '^#[a-f0-9]{6}$'), + primary_a700 varchar(7) CHECK (primary_a700 is null or primary_a700 ~* '^#[a-f0-9]{6}$'), + warn_100 varchar(7) CHECK (warn_100 is null or warn_100 ~* '^#[a-f0-9]{6}$'), + warn_200 varchar(7) CHECK (warn_200 is null or warn_200 ~* '^#[a-f0-9]{6}$'), + warn_300 varchar(7) CHECK (warn_300 is null or warn_300 ~* '^#[a-f0-9]{6}$'), + warn_400 varchar(7) CHECK (warn_400 is null or warn_400 ~* '^#[a-f0-9]{6}$'), + warn_50 varchar(7) CHECK (warn_50 is null or warn_50 ~* '^#[a-f0-9]{6}$'), + warn_500 varchar(7) CHECK (warn_500 is null or warn_500 ~* '^#[a-f0-9]{6}$') DEFAULT '#f06400', + warn_600 varchar(7) CHECK (warn_600 is null or warn_600 ~* '^#[a-f0-9]{6}$'), + warn_700 varchar(7) CHECK (warn_700 is null or warn_700 ~* '^#[a-f0-9]{6}$'), + warn_800 varchar(7) CHECK (warn_800 is null or warn_800 ~* '^#[a-f0-9]{6}$'), + warn_900 varchar(7) CHECK (warn_900 is null or warn_900 ~* '^#[a-f0-9]{6}$'), + warn_a100 varchar(7) CHECK (warn_a100 is null or warn_a100 ~* '^#[a-f0-9]{6}$'), + warn_a200 varchar(7) CHECK (warn_a200 is null or warn_a200 ~* '^#[a-f0-9]{6}$'), + warn_a400 varchar(7) CHECK (warn_a400 is null or warn_a400 ~* '^#[a-f0-9]{6}$'), + warn_a700 varchar(7) CHECK (warn_a700 is null or warn_a700 ~* '^#[a-f0-9]{6}$'), + headbar varchar(7) CHECK (headbar is null or headbar ~* '^#[a-f0-9]{6}$'), + yes varchar(7) CHECK (yes is null or yes ~* '^#[a-f0-9]{6}$'), + no varchar(7) CHECK (no is null or no ~* '^#[a-f0-9]{6}$'), + abstain varchar(7) CHECK (abstain is null or abstain ~* '^#[a-f0-9]{6}$'), + organization_id integer GENERATED ALWAYS AS (1) STORED NOT NULL +); + + + + +CREATE TABLE topic_t ( + id integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY NOT NULL, + title varchar(256) NOT NULL, + text text, + sequential_number integer NOT NULL, + CONSTRAINT unique_topic_sequential_number UNIQUE (sequential_number, meeting_id), + meeting_id integer NOT NULL +); + + + +comment on column topic_t.sequential_number is 'The (positive) serial number of this model in its meeting. This number is auto-generated and read-only.'; + + +CREATE TABLE user_t ( + id integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY NOT NULL, + username varchar(256) NOT NULL, + member_number varchar(256), + saml_id varchar(256) CONSTRAINT minlength_saml_id CHECK (char_length(saml_id) >= 1), + pronoun varchar(32), + title varchar(256), + first_name varchar(256), + last_name varchar(256), + is_active boolean DEFAULT True, + is_physical_person boolean DEFAULT True, + password varchar(256), + default_password varchar(256), + can_change_own_password boolean DEFAULT True, + email varchar(256), + default_vote_weight decimal(16,6) CONSTRAINT minimum_default_vote_weight CHECK (default_vote_weight >= 0.000001) DEFAULT '1.000000', + last_email_sent timestamptz, + is_demo_user boolean, + last_login timestamptz, + external boolean, + gender_id integer, + organization_management_level varchar(256) CONSTRAINT enum_user_organization_management_level CHECK (organization_management_level IN ('superadmin', 'can_manage_organization', 'can_manage_users')), + home_committee_id integer, + organization_id integer GENERATED ALWAYS AS (1) STORED NOT NULL +); + + + +comment on column user_t.saml_id is 'unique-key from IdP for SAML login'; +comment on column user_t.organization_management_level is 'Hierarchical permission level for the whole organization.'; + + +CREATE TABLE vote_t ( + id integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY NOT NULL, + weight decimal(16,6), + value varchar(256), + user_token varchar(256) NOT NULL, + option_id integer NOT NULL, + user_id integer, + delegated_user_id integer, + meeting_id integer NOT NULL +); + + + + + +-- Intermediate table definitions + +CREATE TABLE nm_chat_group_read_group_ids_group_t ( + chat_group_id integer NOT NULL REFERENCES chat_group_t (id) ON DELETE CASCADE INITIALLY DEFERRED, + group_id integer NOT NULL REFERENCES group_t (id) ON DELETE CASCADE INITIALLY DEFERRED, + PRIMARY KEY (chat_group_id, group_id) +); +CREATE INDEX ON nm_chat_group_read_group_ids_group_t (chat_group_id); +CREATE INDEX ON nm_chat_group_read_group_ids_group_t (group_id); + +CREATE TABLE nm_chat_group_write_group_ids_group_t ( + chat_group_id integer NOT NULL REFERENCES chat_group_t (id) ON DELETE CASCADE INITIALLY DEFERRED, + group_id integer NOT NULL REFERENCES group_t (id) ON DELETE CASCADE INITIALLY DEFERRED, + PRIMARY KEY (chat_group_id, group_id) +); +CREATE INDEX ON nm_chat_group_write_group_ids_group_t (chat_group_id); +CREATE INDEX ON nm_chat_group_write_group_ids_group_t (group_id); + +CREATE TABLE nm_committee_manager_ids_user_t ( + committee_id integer NOT NULL REFERENCES committee_t (id) ON DELETE CASCADE INITIALLY DEFERRED, + user_id integer NOT NULL REFERENCES user_t (id) ON DELETE CASCADE INITIALLY DEFERRED, + PRIMARY KEY (committee_id, user_id) +); +CREATE INDEX ON nm_committee_manager_ids_user_t (committee_id); +CREATE INDEX ON nm_committee_manager_ids_user_t (user_id); + +CREATE TABLE nm_committee_all_child_ids_committee_t ( + all_child_id integer NOT NULL REFERENCES committee_t (id) ON DELETE CASCADE INITIALLY DEFERRED, + all_parent_id integer NOT NULL REFERENCES committee_t (id) ON DELETE CASCADE INITIALLY DEFERRED, + PRIMARY KEY (all_child_id, all_parent_id) +); +CREATE INDEX ON nm_committee_all_child_ids_committee_t (all_child_id); +CREATE INDEX ON nm_committee_all_child_ids_committee_t (all_parent_id); + +CREATE TABLE nm_committee_forward_to_committee_ids_committee_t ( + forward_to_committee_id integer NOT NULL REFERENCES committee_t (id) ON DELETE CASCADE INITIALLY DEFERRED, + receive_forwardings_from_committee_id integer NOT NULL REFERENCES committee_t (id) ON DELETE CASCADE INITIALLY DEFERRED, + PRIMARY KEY (forward_to_committee_id, receive_forwardings_from_committee_id) +); +CREATE INDEX ON nm_committee_forward_to_committee_ids_committee_t (forward_to_committee_id); +CREATE INDEX ON nm_committee_forward_to_committee_ids_committee_t (receive_forwardings_from_committee_id); + +CREATE TABLE nm_group_meeting_user_ids_meeting_user_t ( + group_id integer NOT NULL REFERENCES group_t (id) ON DELETE CASCADE INITIALLY DEFERRED, + meeting_user_id integer NOT NULL REFERENCES meeting_user_t (id) ON DELETE CASCADE INITIALLY DEFERRED, + PRIMARY KEY (group_id, meeting_user_id) +); +CREATE INDEX ON nm_group_meeting_user_ids_meeting_user_t (group_id); +CREATE INDEX ON nm_group_meeting_user_ids_meeting_user_t (meeting_user_id); + +CREATE TABLE nm_group_mmagi_meeting_mediafile_t ( + group_id integer NOT NULL REFERENCES group_t (id) ON DELETE CASCADE INITIALLY DEFERRED, + meeting_mediafile_id integer NOT NULL REFERENCES meeting_mediafile_t (id) ON DELETE CASCADE INITIALLY DEFERRED, + PRIMARY KEY (group_id, meeting_mediafile_id) +); +CREATE INDEX ON nm_group_mmagi_meeting_mediafile_t (group_id); +CREATE INDEX ON nm_group_mmagi_meeting_mediafile_t (meeting_mediafile_id); + +CREATE TABLE nm_group_mmiagi_meeting_mediafile_t ( + group_id integer NOT NULL REFERENCES group_t (id) ON DELETE CASCADE INITIALLY DEFERRED, + meeting_mediafile_id integer NOT NULL REFERENCES meeting_mediafile_t (id) ON DELETE CASCADE INITIALLY DEFERRED, + PRIMARY KEY (group_id, meeting_mediafile_id) +); +CREATE INDEX ON nm_group_mmiagi_meeting_mediafile_t (group_id); +CREATE INDEX ON nm_group_mmiagi_meeting_mediafile_t (meeting_mediafile_id); + +CREATE TABLE nm_group_read_comment_section_ids_motion_comment_section_t ( + group_id integer NOT NULL REFERENCES group_t (id) ON DELETE CASCADE INITIALLY DEFERRED, + motion_comment_section_id integer NOT NULL REFERENCES motion_comment_section_t (id) ON DELETE CASCADE INITIALLY DEFERRED, + PRIMARY KEY (group_id, motion_comment_section_id) +); +CREATE INDEX ON nm_group_read_comment_section_ids_motion_comment_section_t (group_id); +CREATE INDEX ON nm_group_read_comment_section_ids_motion_comment_section_t (motion_comment_section_id); + +CREATE TABLE nm_group_write_comment_section_ids_motion_comment_section_t ( + group_id integer NOT NULL REFERENCES group_t (id) ON DELETE CASCADE INITIALLY DEFERRED, + motion_comment_section_id integer NOT NULL REFERENCES motion_comment_section_t (id) ON DELETE CASCADE INITIALLY DEFERRED, + PRIMARY KEY (group_id, motion_comment_section_id) +); +CREATE INDEX ON nm_group_write_comment_section_ids_motion_comment_section_t (group_id); +CREATE INDEX ON nm_group_write_comment_section_ids_motion_comment_section_t (motion_comment_section_id); + +CREATE TABLE nm_group_poll_ids_poll_t ( + group_id integer NOT NULL REFERENCES group_t (id) ON DELETE CASCADE INITIALLY DEFERRED, + poll_id integer NOT NULL REFERENCES poll_t (id) ON DELETE CASCADE INITIALLY DEFERRED, + PRIMARY KEY (group_id, poll_id) +); +CREATE INDEX ON nm_group_poll_ids_poll_t (group_id); +CREATE INDEX ON nm_group_poll_ids_poll_t (poll_id); + +CREATE TABLE nm_meeting_present_user_ids_user_t ( + meeting_id integer NOT NULL REFERENCES meeting_t (id) ON DELETE CASCADE INITIALLY DEFERRED, + user_id integer NOT NULL REFERENCES user_t (id) ON DELETE CASCADE INITIALLY DEFERRED, + PRIMARY KEY (meeting_id, user_id) +); +CREATE INDEX ON nm_meeting_present_user_ids_user_t (meeting_id); +CREATE INDEX ON nm_meeting_present_user_ids_user_t (user_id); + +CREATE TABLE gm_meeting_mediafile_attachment_ids_t ( + meeting_mediafile_id integer NOT NULL REFERENCES meeting_mediafile_t(id) ON DELETE CASCADE INITIALLY DEFERRED, + attachment_id varchar(100) NOT NULL, + attachment_id_motion_id integer GENERATED ALWAYS AS (CASE WHEN split_part(attachment_id, '/', 1) = 'motion' THEN cast(split_part(attachment_id, '/', 2) AS INTEGER) ELSE null END) STORED REFERENCES motion_t(id) ON DELETE CASCADE INITIALLY DEFERRED, + attachment_id_topic_id integer GENERATED ALWAYS AS (CASE WHEN split_part(attachment_id, '/', 1) = 'topic' THEN cast(split_part(attachment_id, '/', 2) AS INTEGER) ELSE null END) STORED REFERENCES topic_t(id) ON DELETE CASCADE INITIALLY DEFERRED, + attachment_id_assignment_id integer GENERATED ALWAYS AS (CASE WHEN split_part(attachment_id, '/', 1) = 'assignment' THEN cast(split_part(attachment_id, '/', 2) AS INTEGER) ELSE null END) STORED REFERENCES assignment_t(id) ON DELETE CASCADE INITIALLY DEFERRED, + CONSTRAINT valid_attachment_id_part1 CHECK (split_part(attachment_id, '/', 1) IN ('motion', 'topic', 'assignment')), + CONSTRAINT unique_$meeting_mediafile_id_$attachment_id UNIQUE (meeting_mediafile_id, attachment_id) +); +CREATE INDEX ON gm_meeting_mediafile_attachment_ids_t (meeting_mediafile_id); +CREATE INDEX ON gm_meeting_mediafile_attachment_ids_t (attachment_id); + +CREATE TABLE nm_meeting_user_structure_level_ids_structure_level_t ( + meeting_user_id integer NOT NULL REFERENCES meeting_user_t (id) ON DELETE CASCADE INITIALLY DEFERRED, + structure_level_id integer NOT NULL REFERENCES structure_level_t (id) ON DELETE CASCADE INITIALLY DEFERRED, + PRIMARY KEY (meeting_user_id, structure_level_id) +); +CREATE INDEX ON nm_meeting_user_structure_level_ids_structure_level_t (meeting_user_id); +CREATE INDEX ON nm_meeting_user_structure_level_ids_structure_level_t (structure_level_id); + +CREATE TABLE nm_motion_all_derived_motion_ids_motion_t ( + all_derived_motion_id integer NOT NULL REFERENCES motion_t (id) ON DELETE CASCADE INITIALLY DEFERRED, + all_origin_id integer NOT NULL REFERENCES motion_t (id) ON DELETE CASCADE INITIALLY DEFERRED, + PRIMARY KEY (all_derived_motion_id, all_origin_id) +); +CREATE INDEX ON nm_motion_all_derived_motion_ids_motion_t (all_derived_motion_id); +CREATE INDEX ON nm_motion_all_derived_motion_ids_motion_t (all_origin_id); + +CREATE TABLE nm_motion_identical_motion_ids_motion_t ( + identical_motion_id_1 integer NOT NULL REFERENCES motion_t (id) ON DELETE CASCADE INITIALLY DEFERRED, + identical_motion_id_2 integer NOT NULL REFERENCES motion_t (id) ON DELETE CASCADE INITIALLY DEFERRED, + PRIMARY KEY (identical_motion_id_1, identical_motion_id_2) +); +CREATE INDEX ON nm_motion_identical_motion_ids_motion_t (identical_motion_id_1); +CREATE INDEX ON nm_motion_identical_motion_ids_motion_t (identical_motion_id_2); + +CREATE TABLE gm_motion_state_extension_reference_ids_t ( + motion_id integer NOT NULL REFERENCES motion_t(id) ON DELETE CASCADE INITIALLY DEFERRED, + state_extension_reference_id varchar(100) NOT NULL, + state_extension_reference_id_motion_id integer GENERATED ALWAYS AS (CASE WHEN split_part(state_extension_reference_id, '/', 1) = 'motion' THEN cast(split_part(state_extension_reference_id, '/', 2) AS INTEGER) ELSE null END) STORED REFERENCES motion_t(id) ON DELETE CASCADE INITIALLY DEFERRED, + CONSTRAINT valid_state_extension_reference_id_part1 CHECK (split_part(state_extension_reference_id, '/', 1) IN ('motion')), + CONSTRAINT unique_$motion_id_$state_extension_reference_id UNIQUE (motion_id, state_extension_reference_id) +); +CREATE INDEX ON gm_motion_state_extension_reference_ids_t (motion_id); +CREATE INDEX ON gm_motion_state_extension_reference_ids_t (state_extension_reference_id); + +CREATE TABLE gm_motion_recommendation_extension_reference_ids_t ( + motion_id integer NOT NULL REFERENCES motion_t(id) ON DELETE CASCADE INITIALLY DEFERRED, + recommendation_extension_reference_id varchar(100) NOT NULL, + recommendation_extension_reference_id_motion_id integer GENERATED ALWAYS AS (CASE WHEN split_part(recommendation_extension_reference_id, '/', 1) = 'motion' THEN cast(split_part(recommendation_extension_reference_id, '/', 2) AS INTEGER) ELSE null END) STORED REFERENCES motion_t(id) ON DELETE CASCADE INITIALLY DEFERRED, + CONSTRAINT valid_recommendation_extension_reference_id_part1 CHECK (split_part(recommendation_extension_reference_id, '/', 1) IN ('motion')), + CONSTRAINT unique_$motion_id_$recommendation_extension_reference_id UNIQUE (motion_id, recommendation_extension_reference_id) +); +CREATE INDEX ON gm_motion_recommendation_extension_reference_ids_t (motion_id); +CREATE INDEX ON gm_motion_recommendation_extension_reference_ids_t (recommendation_extension_reference_id); + +CREATE TABLE nm_motion_state_next_state_ids_motion_state_t ( + next_state_id integer NOT NULL REFERENCES motion_state_t (id) ON DELETE CASCADE INITIALLY DEFERRED, + previous_state_id integer NOT NULL REFERENCES motion_state_t (id) ON DELETE CASCADE INITIALLY DEFERRED, + PRIMARY KEY (next_state_id, previous_state_id) +); +CREATE INDEX ON nm_motion_state_next_state_ids_motion_state_t (next_state_id); +CREATE INDEX ON nm_motion_state_next_state_ids_motion_state_t (previous_state_id); + +CREATE TABLE gm_organization_tag_tagged_ids_t ( + organization_tag_id integer NOT NULL REFERENCES organization_tag_t(id) ON DELETE CASCADE INITIALLY DEFERRED, + tagged_id varchar(100) NOT NULL, + tagged_id_committee_id integer GENERATED ALWAYS AS (CASE WHEN split_part(tagged_id, '/', 1) = 'committee' THEN cast(split_part(tagged_id, '/', 2) AS INTEGER) ELSE null END) STORED REFERENCES committee_t(id) ON DELETE CASCADE INITIALLY DEFERRED, + tagged_id_meeting_id integer GENERATED ALWAYS AS (CASE WHEN split_part(tagged_id, '/', 1) = 'meeting' THEN cast(split_part(tagged_id, '/', 2) AS INTEGER) ELSE null END) STORED REFERENCES meeting_t(id) ON DELETE CASCADE INITIALLY DEFERRED, + CONSTRAINT valid_tagged_id_part1 CHECK (split_part(tagged_id, '/', 1) IN ('committee', 'meeting')), + CONSTRAINT unique_$organization_tag_id_$tagged_id UNIQUE (organization_tag_id, tagged_id) +); +CREATE INDEX ON gm_organization_tag_tagged_ids_t (organization_tag_id); +CREATE INDEX ON gm_organization_tag_tagged_ids_t (tagged_id); + +CREATE TABLE nm_poll_voted_ids_user_t ( + poll_id integer NOT NULL REFERENCES poll_t (id) ON DELETE CASCADE INITIALLY DEFERRED, + user_id integer NOT NULL REFERENCES user_t (id) ON DELETE CASCADE INITIALLY DEFERRED, + PRIMARY KEY (poll_id, user_id) +); +CREATE INDEX ON nm_poll_voted_ids_user_t (poll_id); +CREATE INDEX ON nm_poll_voted_ids_user_t (user_id); + +CREATE TABLE gm_tag_tagged_ids_t ( + tag_id integer NOT NULL REFERENCES tag_t(id) ON DELETE CASCADE INITIALLY DEFERRED, + tagged_id varchar(100) NOT NULL, + tagged_id_agenda_item_id integer GENERATED ALWAYS AS (CASE WHEN split_part(tagged_id, '/', 1) = 'agenda_item' THEN cast(split_part(tagged_id, '/', 2) AS INTEGER) ELSE null END) STORED REFERENCES agenda_item_t(id) ON DELETE CASCADE INITIALLY DEFERRED, + tagged_id_assignment_id integer GENERATED ALWAYS AS (CASE WHEN split_part(tagged_id, '/', 1) = 'assignment' THEN cast(split_part(tagged_id, '/', 2) AS INTEGER) ELSE null END) STORED REFERENCES assignment_t(id) ON DELETE CASCADE INITIALLY DEFERRED, + tagged_id_motion_id integer GENERATED ALWAYS AS (CASE WHEN split_part(tagged_id, '/', 1) = 'motion' THEN cast(split_part(tagged_id, '/', 2) AS INTEGER) ELSE null END) STORED REFERENCES motion_t(id) ON DELETE CASCADE INITIALLY DEFERRED, + CONSTRAINT valid_tagged_id_part1 CHECK (split_part(tagged_id, '/', 1) IN ('agenda_item', 'assignment', 'motion')), + CONSTRAINT unique_$tag_id_$tagged_id UNIQUE (tag_id, tagged_id) +); +CREATE INDEX ON gm_tag_tagged_ids_t (tag_id); +CREATE INDEX ON gm_tag_tagged_ids_t (tagged_id); + + +-- View definitions + +CREATE VIEW "action_worker" AS SELECT * FROM action_worker_t a; + + +CREATE VIEW "agenda_item" AS SELECT *, +(select array_agg(ai.id ORDER BY ai.id) from agenda_item_t ai where ai.parent_id = a.id) as child_ids, +(select array_agg(g.tag_id ORDER BY g.tag_id) from gm_tag_tagged_ids_t g where g.tagged_id_agenda_item_id = a.id) as tag_ids, +(select array_agg(p.id ORDER BY p.id) from projection_t p where p.content_object_id_agenda_item_id = a.id) as projection_ids +FROM agenda_item_t a; + + +CREATE VIEW "assignment" AS SELECT *, +(select array_agg(ac.id ORDER BY ac.id) from assignment_candidate_t ac where ac.assignment_id = a.id) as candidate_ids, +(select array_agg(p.id ORDER BY p.id) from poll_t p where p.content_object_id_assignment_id = a.id) as poll_ids, +(select ai.id from agenda_item_t ai where ai.content_object_id_assignment_id = a.id) as agenda_item_id, +(select l.id from list_of_speakers_t l where l.content_object_id_assignment_id = a.id) as list_of_speakers_id, +(select array_agg(g.tag_id ORDER BY g.tag_id) from gm_tag_tagged_ids_t g where g.tagged_id_assignment_id = a.id) as tag_ids, +(select array_agg(g.meeting_mediafile_id ORDER BY g.meeting_mediafile_id) from gm_meeting_mediafile_attachment_ids_t g where g.attachment_id_assignment_id = a.id) as attachment_meeting_mediafile_ids, +(select array_agg(p.id ORDER BY p.id) from projection_t p where p.content_object_id_assignment_id = a.id) as projection_ids, +(select array_agg(h.id ORDER BY h.id) from history_entry_t h where h.model_id_assignment_id = a.id) as history_entry_ids +FROM assignment_t a; + + +CREATE VIEW "assignment_candidate" AS SELECT * FROM assignment_candidate_t a; + + +CREATE VIEW "chat_group" AS SELECT *, +(select array_agg(cm.id ORDER BY cm.id) from chat_message_t cm where cm.chat_group_id = c.id) as chat_message_ids, +(select array_agg(n.group_id ORDER BY n.group_id) from nm_chat_group_read_group_ids_group_t n where n.chat_group_id = c.id) as read_group_ids, +(select array_agg(n.group_id ORDER BY n.group_id) from nm_chat_group_write_group_ids_group_t n where n.chat_group_id = c.id) as write_group_ids +FROM chat_group_t c; + + +CREATE VIEW "chat_message" AS SELECT * FROM chat_message_t c; + + +CREATE VIEW "committee" AS SELECT *, +(select array_agg(m.id ORDER BY m.id) from meeting_t m where m.committee_id = c.id) as meeting_ids, +( + SELECT array_agg(DISTINCT user_id ORDER BY user_id) + FROM ( + -- Select user_ids from committees meetings + SELECT mu.user_id + FROM meeting_t AS m + INNER JOIN meeting_user_t AS mu ON mu.meeting_id = m.id + WHERE m.committee_id = c.id + + UNION + + -- Select user_ids from committee managers + SELECT cmu.user_id + FROM nm_committee_manager_ids_user_t cmu + WHERE cmu.committee_id = c.id + + UNION + + -- Select user_id from home committees + SELECT u.id FROM user_t u WHERE u.home_committee_id = c.id + ) _ +) AS user_ids +, +(select array_agg(n.user_id ORDER BY n.user_id) from nm_committee_manager_ids_user_t n where n.committee_id = c.id) as manager_ids, +(select array_agg(ct.id ORDER BY ct.id) from committee_t ct where ct.parent_id = c.id) as child_ids, +(select array_agg(n.all_parent_id ORDER BY n.all_parent_id) from nm_committee_all_child_ids_committee_t n where n.all_child_id = c.id) as all_parent_ids, +(select array_agg(n.all_child_id ORDER BY n.all_child_id) from nm_committee_all_child_ids_committee_t n where n.all_parent_id = c.id) as all_child_ids, +(select array_agg(u.id ORDER BY u.id) from user_t u where u.home_committee_id = c.id) as native_user_ids, +(select array_agg(n.forward_to_committee_id ORDER BY n.forward_to_committee_id) from nm_committee_forward_to_committee_ids_committee_t n where n.receive_forwardings_from_committee_id = c.id) as forward_to_committee_ids, +(select array_agg(n.receive_forwardings_from_committee_id ORDER BY n.receive_forwardings_from_committee_id) from nm_committee_forward_to_committee_ids_committee_t n where n.forward_to_committee_id = c.id) as receive_forwardings_from_committee_ids, +(select array_agg(g.organization_tag_id ORDER BY g.organization_tag_id) from gm_organization_tag_tagged_ids_t g where g.tagged_id_committee_id = c.id) as organization_tag_ids +FROM committee_t c; + +comment on column "committee".user_ids is 'Calculated field: All users which are in a group of a meeting, belonging to the committee or beeing manager of the committee'; + +CREATE VIEW "gender" AS SELECT *, +(select array_agg(u.id ORDER BY u.id) from user_t u where u.gender_id = g.id) as user_ids +FROM gender_t g; + + +CREATE VIEW "group" AS SELECT *, +(select array_agg(n.meeting_user_id ORDER BY n.meeting_user_id) from nm_group_meeting_user_ids_meeting_user_t n where n.group_id = g.id) as meeting_user_ids, +(select m.id from meeting_t m where m.default_group_id = g.id) as default_group_for_meeting_id, +(select m.id from meeting_t m where m.admin_group_id = g.id) as admin_group_for_meeting_id, +(select m.id from meeting_t m where m.anonymous_group_id = g.id) as anonymous_group_for_meeting_id, +(select array_agg(n.meeting_mediafile_id ORDER BY n.meeting_mediafile_id) from nm_group_mmagi_meeting_mediafile_t n where n.group_id = g.id) as meeting_mediafile_access_group_ids, +(select array_agg(n.meeting_mediafile_id ORDER BY n.meeting_mediafile_id) from nm_group_mmiagi_meeting_mediafile_t n where n.group_id = g.id) as meeting_mediafile_inherited_access_group_ids, +(select array_agg(n.motion_comment_section_id ORDER BY n.motion_comment_section_id) from nm_group_read_comment_section_ids_motion_comment_section_t n where n.group_id = g.id) as read_comment_section_ids, +(select array_agg(n.motion_comment_section_id ORDER BY n.motion_comment_section_id) from nm_group_write_comment_section_ids_motion_comment_section_t n where n.group_id = g.id) as write_comment_section_ids, +(select array_agg(n.chat_group_id ORDER BY n.chat_group_id) from nm_chat_group_read_group_ids_group_t n where n.group_id = g.id) as read_chat_group_ids, +(select array_agg(n.chat_group_id ORDER BY n.chat_group_id) from nm_chat_group_write_group_ids_group_t n where n.group_id = g.id) as write_chat_group_ids, +(select array_agg(n.poll_id ORDER BY n.poll_id) from nm_group_poll_ids_poll_t n where n.group_id = g.id) as poll_ids +FROM group_t g; + +comment on column "group".meeting_mediafile_inherited_access_group_ids is 'Calculated field.'; + +CREATE VIEW "history_entry" AS SELECT * FROM history_entry_t h; + + +CREATE VIEW "history_position" AS SELECT *, +(select array_agg(he.id ORDER BY he.id) from history_entry_t he where he.position_id = h.id) as entry_ids +FROM history_position_t h; + + +CREATE VIEW "import_preview" AS SELECT * FROM import_preview_t i; + + +CREATE VIEW "list_of_speakers" AS SELECT *, +(select array_agg(s.id ORDER BY s.id) from speaker_t s where s.list_of_speakers_id = l.id) as speaker_ids, +(select array_agg(s.id ORDER BY s.id) from structure_level_list_of_speakers_t s where s.list_of_speakers_id = l.id) as structure_level_list_of_speakers_ids, +(select array_agg(p.id ORDER BY p.id) from projection_t p where p.content_object_id_list_of_speakers_id = l.id) as projection_ids +FROM list_of_speakers_t l; + + +CREATE VIEW "mediafile" AS SELECT *, +(select array_agg(mt.id ORDER BY mt.id) from mediafile_t mt where mt.parent_id = m.id) as child_ids, +(select array_agg(mm.id ORDER BY mm.id) from meeting_mediafile_t mm where mm.mediafile_id = m.id) as meeting_mediafile_ids +FROM mediafile_t m; + + +CREATE VIEW "meeting" AS SELECT *, +(select array_agg(g.id ORDER BY g.id) from group_t g where g.used_as_motion_poll_default_id = m.id) as motion_poll_default_group_ids, +(select array_agg(p.id ORDER BY p.id) from poll_candidate_list_t p where p.meeting_id = m.id) as poll_candidate_list_ids, +(select array_agg(p.id ORDER BY p.id) from poll_candidate_t p where p.meeting_id = m.id) as poll_candidate_ids, +(select array_agg(mu.id ORDER BY mu.id) from meeting_user_t mu where mu.meeting_id = m.id) as meeting_user_ids, +(select array_agg(g.id ORDER BY g.id) from group_t g where g.used_as_assignment_poll_default_id = m.id) as assignment_poll_default_group_ids, +(select array_agg(g.id ORDER BY g.id) from group_t g where g.used_as_poll_default_id = m.id) as poll_default_group_ids, +(select array_agg(g.id ORDER BY g.id) from group_t g where g.used_as_topic_poll_default_id = m.id) as topic_poll_default_group_ids, +(select array_agg(p.id ORDER BY p.id) from projector_t p where p.meeting_id = m.id) as projector_ids, +(select array_agg(p.id ORDER BY p.id) from projection_t p where p.meeting_id = m.id) as all_projection_ids, +(select array_agg(p.id ORDER BY p.id) from projector_message_t p where p.meeting_id = m.id) as projector_message_ids, +(select array_agg(p.id ORDER BY p.id) from projector_countdown_t p where p.meeting_id = m.id) as projector_countdown_ids, +(select array_agg(t.id ORDER BY t.id) from tag_t t where t.meeting_id = m.id) as tag_ids, +(select array_agg(a.id ORDER BY a.id) from agenda_item_t a where a.meeting_id = m.id) as agenda_item_ids, +(select array_agg(l.id ORDER BY l.id) from list_of_speakers_t l where l.meeting_id = m.id) as list_of_speakers_ids, +(select array_agg(s.id ORDER BY s.id) from structure_level_list_of_speakers_t s where s.meeting_id = m.id) as structure_level_list_of_speakers_ids, +(select array_agg(p.id ORDER BY p.id) from point_of_order_category_t p where p.meeting_id = m.id) as point_of_order_category_ids, +(select array_agg(s.id ORDER BY s.id) from speaker_t s where s.meeting_id = m.id) as speaker_ids, +(select array_agg(t.id ORDER BY t.id) from topic_t t where t.meeting_id = m.id) as topic_ids, +(select array_agg(g.id ORDER BY g.id) from group_t g where g.meeting_id = m.id) as group_ids, +(select array_agg(mm.id ORDER BY mm.id) from meeting_mediafile_t mm where mm.meeting_id = m.id) as meeting_mediafile_ids, +(select array_agg(mt.id ORDER BY mt.id) from mediafile_t mt where mt.owner_id_meeting_id = m.id) as mediafile_ids, +(select array_agg(mt.id ORDER BY mt.id) from motion_t mt where mt.meeting_id = m.id) as motion_ids, +(select array_agg(mt.id ORDER BY mt.id) from motion_t mt where mt.origin_meeting_id = m.id) as forwarded_motion_ids, +(select array_agg(mc.id ORDER BY mc.id) from motion_comment_section_t mc where mc.meeting_id = m.id) as motion_comment_section_ids, +(select array_agg(mc.id ORDER BY mc.id) from motion_category_t mc where mc.meeting_id = m.id) as motion_category_ids, +(select array_agg(mb.id ORDER BY mb.id) from motion_block_t mb where mb.meeting_id = m.id) as motion_block_ids, +(select array_agg(mw.id ORDER BY mw.id) from motion_workflow_t mw where mw.meeting_id = m.id) as motion_workflow_ids, +(select array_agg(mc.id ORDER BY mc.id) from motion_comment_t mc where mc.meeting_id = m.id) as motion_comment_ids, +(select array_agg(ms.id ORDER BY ms.id) from motion_submitter_t ms where ms.meeting_id = m.id) as motion_submitter_ids, +(select array_agg(ms.id ORDER BY ms.id) from motion_supporter_t ms where ms.meeting_id = m.id) as motion_supporter_ids, +(select array_agg(me.id ORDER BY me.id) from motion_editor_t me where me.meeting_id = m.id) as motion_editor_ids, +(select array_agg(mw.id ORDER BY mw.id) from motion_working_group_speaker_t mw where mw.meeting_id = m.id) as motion_working_group_speaker_ids, +(select array_agg(mc.id ORDER BY mc.id) from motion_change_recommendation_t mc where mc.meeting_id = m.id) as motion_change_recommendation_ids, +(select array_agg(ms.id ORDER BY ms.id) from motion_state_t ms where ms.meeting_id = m.id) as motion_state_ids, +(select array_agg(p.id ORDER BY p.id) from poll_t p where p.meeting_id = m.id) as poll_ids, +(select array_agg(o.id ORDER BY o.id) from option_t o where o.meeting_id = m.id) as option_ids, +(select array_agg(v.id ORDER BY v.id) from vote_t v where v.meeting_id = m.id) as vote_ids, +(select array_agg(a.id ORDER BY a.id) from assignment_t a where a.meeting_id = m.id) as assignment_ids, +(select array_agg(a.id ORDER BY a.id) from assignment_candidate_t a where a.meeting_id = m.id) as assignment_candidate_ids, +(select array_agg(p.id ORDER BY p.id) from personal_note_t p where p.meeting_id = m.id) as personal_note_ids, +(select array_agg(c.id ORDER BY c.id) from chat_group_t c where c.meeting_id = m.id) as chat_group_ids, +(select array_agg(c.id ORDER BY c.id) from chat_message_t c where c.meeting_id = m.id) as chat_message_ids, +(select array_agg(s.id ORDER BY s.id) from structure_level_t s where s.meeting_id = m.id) as structure_level_ids, +(select c.id from committee_t c where c.default_meeting_id = m.id) as default_meeting_for_committee_id, +(select array_agg(g.organization_tag_id ORDER BY g.organization_tag_id) from gm_organization_tag_tagged_ids_t g where g.tagged_id_meeting_id = m.id) as organization_tag_ids, +(select array_agg(n.user_id ORDER BY n.user_id) from nm_meeting_present_user_ids_user_t n where n.meeting_id = m.id) as present_user_ids, +( + SELECT array_agg(DISTINCT mu.user_id ORDER BY mu.user_id) + FROM meeting_user_t mu + WHERE mu.meeting_id = m.id +) AS user_ids +, +(select array_agg(p.id ORDER BY p.id) from projection_t p where p.content_object_id_meeting_id = m.id) as projection_ids, +(select array_agg(p.id ORDER BY p.id) from projector_t p where p.used_as_default_projector_for_agenda_item_list_in_meeting_id = m.id) as default_projector_agenda_item_list_ids, +(select array_agg(p.id ORDER BY p.id) from projector_t p where p.used_as_default_projector_for_topic_in_meeting_id = m.id) as default_projector_topic_ids, +(select array_agg(p.id ORDER BY p.id) from projector_t p where p.used_as_default_projector_for_list_of_speakers_in_meeting_id = m.id) as default_projector_list_of_speakers_ids, +(select array_agg(p.id ORDER BY p.id) from projector_t p where p.used_as_default_projector_for_current_los_in_meeting_id = m.id) as default_projector_current_los_ids, +(select array_agg(p.id ORDER BY p.id) from projector_t p where p.used_as_default_projector_for_motion_in_meeting_id = m.id) as default_projector_motion_ids, +(select array_agg(p.id ORDER BY p.id) from projector_t p where p.used_as_default_projector_for_amendment_in_meeting_id = m.id) as default_projector_amendment_ids, +(select array_agg(p.id ORDER BY p.id) from projector_t p where p.used_as_default_projector_for_motion_block_in_meeting_id = m.id) as default_projector_motion_block_ids, +(select array_agg(p.id ORDER BY p.id) from projector_t p where p.used_as_default_projector_for_assignment_in_meeting_id = m.id) as default_projector_assignment_ids, +(select array_agg(p.id ORDER BY p.id) from projector_t p where p.used_as_default_projector_for_mediafile_in_meeting_id = m.id) as default_projector_mediafile_ids, +(select array_agg(p.id ORDER BY p.id) from projector_t p where p.used_as_default_projector_for_message_in_meeting_id = m.id) as default_projector_message_ids, +(select array_agg(p.id ORDER BY p.id) from projector_t p where p.used_as_default_projector_for_countdown_in_meeting_id = m.id) as default_projector_countdown_ids, +(select array_agg(p.id ORDER BY p.id) from projector_t p where p.used_as_default_projector_for_assignment_poll_in_meeting_id = m.id) as default_projector_assignment_poll_ids, +(select array_agg(p.id ORDER BY p.id) from projector_t p where p.used_as_default_projector_for_motion_poll_in_meeting_id = m.id) as default_projector_motion_poll_ids, +(select array_agg(p.id ORDER BY p.id) from projector_t p where p.used_as_default_projector_for_poll_in_meeting_id = m.id) as default_projector_poll_ids, +(select array_agg(h.id ORDER BY h.id) from history_entry_t h where h.meeting_id = m.id) as relevant_history_entry_ids +FROM meeting_t m; + +comment on column "meeting_t".is_active_in_organization_id is 'Backrelation and boolean flag at once'; +comment on column "meeting_t".is_archived_in_organization_id is 'Backrelation and boolean flag at once'; +comment on column "meeting".user_ids is 'Calculated. All user ids from all users assigned to groups of this meeting.'; + +CREATE VIEW "meeting_mediafile" AS SELECT *, +(select array_agg(n.group_id ORDER BY n.group_id) from nm_group_mmiagi_meeting_mediafile_t n where n.meeting_mediafile_id = m.id) as inherited_access_group_ids, +(select array_agg(n.group_id ORDER BY n.group_id) from nm_group_mmagi_meeting_mediafile_t n where n.meeting_mediafile_id = m.id) as access_group_ids, +(select l.id from list_of_speakers_t l where l.content_object_id_meeting_mediafile_id = m.id) as list_of_speakers_id, +(select array_agg(p.id ORDER BY p.id) from projection_t p where p.content_object_id_meeting_mediafile_id = m.id) as projection_ids, +(select array_agg(g.attachment_id ORDER BY g.attachment_id) from gm_meeting_mediafile_attachment_ids_t g where g.meeting_mediafile_id = m.id) as attachment_ids, +(select m1.id from meeting_t m1 where m1.logo_projector_main_id = m.id) as used_as_logo_projector_main_in_meeting_id, +(select m1.id from meeting_t m1 where m1.logo_projector_header_id = m.id) as used_as_logo_projector_header_in_meeting_id, +(select m1.id from meeting_t m1 where m1.logo_web_header_id = m.id) as used_as_logo_web_header_in_meeting_id, +(select m1.id from meeting_t m1 where m1.logo_pdf_header_l_id = m.id) as used_as_logo_pdf_header_l_in_meeting_id, +(select m1.id from meeting_t m1 where m1.logo_pdf_header_r_id = m.id) as used_as_logo_pdf_header_r_in_meeting_id, +(select m1.id from meeting_t m1 where m1.logo_pdf_footer_l_id = m.id) as used_as_logo_pdf_footer_l_in_meeting_id, +(select m1.id from meeting_t m1 where m1.logo_pdf_footer_r_id = m.id) as used_as_logo_pdf_footer_r_in_meeting_id, +(select m1.id from meeting_t m1 where m1.logo_pdf_ballot_paper_id = m.id) as used_as_logo_pdf_ballot_paper_in_meeting_id, +(select m1.id from meeting_t m1 where m1.font_regular_id = m.id) as used_as_font_regular_in_meeting_id, +(select m1.id from meeting_t m1 where m1.font_italic_id = m.id) as used_as_font_italic_in_meeting_id, +(select m1.id from meeting_t m1 where m1.font_bold_id = m.id) as used_as_font_bold_in_meeting_id, +(select m1.id from meeting_t m1 where m1.font_bold_italic_id = m.id) as used_as_font_bold_italic_in_meeting_id, +(select m1.id from meeting_t m1 where m1.font_monospace_id = m.id) as used_as_font_monospace_in_meeting_id, +(select m1.id from meeting_t m1 where m1.font_chyron_speaker_name_id = m.id) as used_as_font_chyron_speaker_name_in_meeting_id, +(select m1.id from meeting_t m1 where m1.font_projector_h1_id = m.id) as used_as_font_projector_h1_in_meeting_id, +(select m1.id from meeting_t m1 where m1.font_projector_h2_id = m.id) as used_as_font_projector_h2_in_meeting_id +FROM meeting_mediafile_t m; + +comment on column "meeting_mediafile".inherited_access_group_ids is 'Calculated in actions. Shows what access group permissions are actually relevant. Calculated as the intersection of this meeting_mediafiles access_group_ids and the related mediafiles potential parent mediafiles inherited_access_group_ids. If the parent has no meeting_mediafile for this meeting, its inherited access group is assumed to be the meetings admin group. If there is no parent, the inherited_access_group_ids is equal to the access_group_ids. If the access_group_ids are empty, the interpretations is that every group has access rights, therefore the parent inherited_access_group_ids are used as-is.'; + +CREATE VIEW "meeting_user" AS SELECT *, +(select array_agg(p.id ORDER BY p.id) from personal_note_t p where p.meeting_user_id = m.id) as personal_note_ids, +(select array_agg(s.id ORDER BY s.id) from speaker_t s where s.meeting_user_id = m.id) as speaker_ids, +(select array_agg(ms.id ORDER BY ms.id) from motion_supporter_t ms where ms.meeting_user_id = m.id) as motion_supporter_ids, +(select array_agg(me.id ORDER BY me.id) from motion_editor_t me where me.meeting_user_id = m.id) as motion_editor_ids, +(select array_agg(mw.id ORDER BY mw.id) from motion_working_group_speaker_t mw where mw.meeting_user_id = m.id) as motion_working_group_speaker_ids, +(select array_agg(ms.id ORDER BY ms.id) from motion_submitter_t ms where ms.meeting_user_id = m.id) as motion_submitter_ids, +(select array_agg(a.id ORDER BY a.id) from assignment_candidate_t a where a.meeting_user_id = m.id) as assignment_candidate_ids, +(select array_agg(mu.id ORDER BY mu.id) from meeting_user_t mu where mu.vote_delegated_to_id = m.id) as vote_delegations_from_ids, +(select array_agg(c.id ORDER BY c.id) from chat_message_t c where c.meeting_user_id = m.id) as chat_message_ids, +(select array_agg(n.group_id ORDER BY n.group_id) from nm_group_meeting_user_ids_meeting_user_t n where n.meeting_user_id = m.id) as group_ids, +(select array_agg(n.structure_level_id ORDER BY n.structure_level_id) from nm_meeting_user_structure_level_ids_structure_level_t n where n.meeting_user_id = m.id) as structure_level_ids +FROM meeting_user_t m; + + +CREATE VIEW "motion" AS SELECT *, +(select array_agg(mt.id ORDER BY mt.id) from motion_t mt where mt.lead_motion_id = m.id) as amendment_ids, +(select array_agg(mt.id ORDER BY mt.id) from motion_t mt where mt.sort_parent_id = m.id) as sort_child_ids, +(select array_agg(mt.id ORDER BY mt.id) from motion_t mt where mt.origin_id = m.id) as derived_motion_ids, +(select array_agg(n.all_origin_id ORDER BY n.all_origin_id) from nm_motion_all_derived_motion_ids_motion_t n where n.all_derived_motion_id = m.id) as all_origin_ids, +(select array_agg(n.all_derived_motion_id ORDER BY n.all_derived_motion_id) from nm_motion_all_derived_motion_ids_motion_t n where n.all_origin_id = m.id) as all_derived_motion_ids, +(select array_cat((select array_agg(n.identical_motion_id_1 ORDER BY n.identical_motion_id_1) from nm_motion_identical_motion_ids_motion_t n where n.identical_motion_id_2 = m.id), (select array_agg(n.identical_motion_id_2 ORDER BY n.identical_motion_id_2) from nm_motion_identical_motion_ids_motion_t n where n.identical_motion_id_1 = m.id))) as identical_motion_ids, +(select array_agg(g.state_extension_reference_id ORDER BY g.state_extension_reference_id) from gm_motion_state_extension_reference_ids_t g where g.motion_id = m.id) as state_extension_reference_ids, +(select array_agg(g.motion_id ORDER BY g.motion_id) from gm_motion_state_extension_reference_ids_t g where g.state_extension_reference_id_motion_id = m.id) as referenced_in_motion_state_extension_ids, +(select array_agg(g.recommendation_extension_reference_id ORDER BY g.recommendation_extension_reference_id) from gm_motion_recommendation_extension_reference_ids_t g where g.motion_id = m.id) as recommendation_extension_reference_ids, +(select array_agg(g.motion_id ORDER BY g.motion_id) from gm_motion_recommendation_extension_reference_ids_t g where g.recommendation_extension_reference_id_motion_id = m.id) as referenced_in_motion_recommendation_extension_ids, +(select array_agg(ms.id ORDER BY ms.id) from motion_submitter_t ms where ms.motion_id = m.id) as submitter_ids, +(select array_agg(ms.id ORDER BY ms.id) from motion_supporter_t ms where ms.motion_id = m.id) as supporter_ids, +(select array_agg(me.id ORDER BY me.id) from motion_editor_t me where me.motion_id = m.id) as editor_ids, +(select array_agg(mw.id ORDER BY mw.id) from motion_working_group_speaker_t mw where mw.motion_id = m.id) as working_group_speaker_ids, +(select array_agg(p.id ORDER BY p.id) from poll_t p where p.content_object_id_motion_id = m.id) as poll_ids, +(select array_agg(o.id ORDER BY o.id) from option_t o where o.content_object_id_motion_id = m.id) as option_ids, +(select array_agg(mc.id ORDER BY mc.id) from motion_change_recommendation_t mc where mc.motion_id = m.id) as change_recommendation_ids, +(select array_agg(mc.id ORDER BY mc.id) from motion_comment_t mc where mc.motion_id = m.id) as comment_ids, +(select a.id from agenda_item_t a where a.content_object_id_motion_id = m.id) as agenda_item_id, +(select l.id from list_of_speakers_t l where l.content_object_id_motion_id = m.id) as list_of_speakers_id, +(select array_agg(g.tag_id ORDER BY g.tag_id) from gm_tag_tagged_ids_t g where g.tagged_id_motion_id = m.id) as tag_ids, +(select array_agg(g.meeting_mediafile_id ORDER BY g.meeting_mediafile_id) from gm_meeting_mediafile_attachment_ids_t g where g.attachment_id_motion_id = m.id) as attachment_meeting_mediafile_ids, +(select array_agg(p.id ORDER BY p.id) from projection_t p where p.content_object_id_motion_id = m.id) as projection_ids, +(select array_agg(p.id ORDER BY p.id) from personal_note_t p where p.content_object_id_motion_id = m.id) as personal_note_ids, +(select array_agg(h.id ORDER BY h.id) from history_entry_t h where h.model_id_motion_id = m.id) as history_entry_ids +FROM motion_t m; + + +CREATE VIEW "motion_block" AS SELECT *, +(select array_agg(mt.id ORDER BY mt.id) from motion_t mt where mt.block_id = m.id) as motion_ids, +(select a.id from agenda_item_t a where a.content_object_id_motion_block_id = m.id) as agenda_item_id, +(select l.id from list_of_speakers_t l where l.content_object_id_motion_block_id = m.id) as list_of_speakers_id, +(select array_agg(p.id ORDER BY p.id) from projection_t p where p.content_object_id_motion_block_id = m.id) as projection_ids +FROM motion_block_t m; + + +CREATE VIEW "motion_category" AS SELECT *, +(select array_agg(mc.id ORDER BY mc.id) from motion_category_t mc where mc.parent_id = m.id) as child_ids, +(select array_agg(mt.id ORDER BY mt.id) from motion_t mt where mt.category_id = m.id) as motion_ids +FROM motion_category_t m; + + +CREATE VIEW "motion_change_recommendation" AS SELECT * FROM motion_change_recommendation_t m; + + +CREATE VIEW "motion_comment" AS SELECT * FROM motion_comment_t m; + + +CREATE VIEW "motion_comment_section" AS SELECT *, +(select array_agg(mc.id ORDER BY mc.id) from motion_comment_t mc where mc.section_id = m.id) as comment_ids, +(select array_agg(n.group_id ORDER BY n.group_id) from nm_group_read_comment_section_ids_motion_comment_section_t n where n.motion_comment_section_id = m.id) as read_group_ids, +(select array_agg(n.group_id ORDER BY n.group_id) from nm_group_write_comment_section_ids_motion_comment_section_t n where n.motion_comment_section_id = m.id) as write_group_ids +FROM motion_comment_section_t m; + + +CREATE VIEW "motion_editor" AS SELECT * FROM motion_editor_t m; + + +CREATE VIEW "motion_state" AS SELECT *, +(select array_agg(ms.id ORDER BY ms.id) from motion_state_t ms where ms.submitter_withdraw_state_id = m.id) as submitter_withdraw_back_ids, +(select array_agg(n.next_state_id ORDER BY n.next_state_id) from nm_motion_state_next_state_ids_motion_state_t n where n.previous_state_id = m.id) as next_state_ids, +(select array_agg(n.previous_state_id ORDER BY n.previous_state_id) from nm_motion_state_next_state_ids_motion_state_t n where n.next_state_id = m.id) as previous_state_ids, +(select array_agg(mt.id ORDER BY mt.id) from motion_t mt where mt.state_id = m.id) as motion_ids, +(select array_agg(mt.id ORDER BY mt.id) from motion_t mt where mt.recommendation_id = m.id) as motion_recommendation_ids, +(select mw.id from motion_workflow_t mw where mw.first_state_id = m.id) as first_state_of_workflow_id +FROM motion_state_t m; + + +CREATE VIEW "motion_submitter" AS SELECT * FROM motion_submitter_t m; + + +CREATE VIEW "motion_supporter" AS SELECT * FROM motion_supporter_t m; + + +CREATE VIEW "motion_workflow" AS SELECT *, +(select array_agg(ms.id ORDER BY ms.id) from motion_state_t ms where ms.workflow_id = m.id) as state_ids, +(select m1.id from meeting_t m1 where m1.motions_default_workflow_id = m.id) as default_workflow_meeting_id, +(select m1.id from meeting_t m1 where m1.motions_default_amendment_workflow_id = m.id) as default_amendment_workflow_meeting_id +FROM motion_workflow_t m; + + +CREATE VIEW "motion_working_group_speaker" AS SELECT * FROM motion_working_group_speaker_t m; + + +CREATE VIEW "option" AS SELECT *, +(select array_agg(v.id ORDER BY v.id) from vote_t v where v.option_id = o.id) as vote_ids +FROM option_t o; + + +CREATE VIEW "organization" AS SELECT *, +(select array_agg(g.id ORDER BY g.id) from gender_t g where g.organization_id = o.id) as gender_ids, +(select array_agg(c.id ORDER BY c.id) from committee_t c where c.organization_id = o.id) as committee_ids, +(select array_agg(m.id ORDER BY m.id) from meeting_t m where m.is_active_in_organization_id = o.id) as active_meeting_ids, +(select array_agg(m.id ORDER BY m.id) from meeting_t m where m.is_archived_in_organization_id = o.id) as archived_meeting_ids, +(select array_agg(m.id ORDER BY m.id) from meeting_t m where m.template_for_organization_id = o.id) as template_meeting_ids, +(select array_agg(ot.id ORDER BY ot.id) from organization_tag_t ot where ot.organization_id = o.id) as organization_tag_ids, +(select array_agg(t.id ORDER BY t.id) from theme_t t where t.organization_id = o.id) as theme_ids, +(select array_agg(m.id ORDER BY m.id) from mediafile_t m where m.owner_id_organization_id = o.id) as mediafile_ids, +(select array_agg(m.id ORDER BY m.id) from mediafile_t m where m.published_to_meetings_in_organization_id = o.id) as published_mediafile_ids, +(select array_agg(u.id ORDER BY u.id) from user_t u where u.organization_id = o.id) as user_ids +FROM organization_t o; + + +CREATE VIEW "organization_tag" AS SELECT *, +(select array_agg(g.tagged_id ORDER BY g.tagged_id) from gm_organization_tag_tagged_ids_t g where g.organization_tag_id = o.id) as tagged_ids +FROM organization_tag_t o; + + +CREATE VIEW "personal_note" AS SELECT * FROM personal_note_t p; + + +CREATE VIEW "point_of_order_category" AS SELECT *, +(select array_agg(s.id ORDER BY s.id) from speaker_t s where s.point_of_order_category_id = p.id) as speaker_ids +FROM point_of_order_category_t p; + + +CREATE VIEW "poll" AS SELECT *, +(select array_agg(o.id ORDER BY o.id) from option_t o where o.poll_id = p.id) as option_ids, +(select array_agg(n.user_id ORDER BY n.user_id) from nm_poll_voted_ids_user_t n where n.poll_id = p.id) as voted_ids, +(select array_agg(n.group_id ORDER BY n.group_id) from nm_group_poll_ids_poll_t n where n.poll_id = p.id) as entitled_group_ids, +(select array_agg(pt.id ORDER BY pt.id) from projection_t pt where pt.content_object_id_poll_id = p.id) as projection_ids +FROM poll_t p; + + +CREATE VIEW "poll_candidate" AS SELECT * FROM poll_candidate_t p; + + +CREATE VIEW "poll_candidate_list" AS SELECT *, +(select array_agg(pc.id ORDER BY pc.id) from poll_candidate_t pc where pc.poll_candidate_list_id = p.id) as poll_candidate_ids, +(select o.id from option_t o where o.content_object_id_poll_candidate_list_id = p.id) as option_id +FROM poll_candidate_list_t p; + + +CREATE VIEW "projection" AS SELECT * FROM projection_t p; + + +CREATE VIEW "projector" AS SELECT *, +(select array_agg(pt.id ORDER BY pt.id) from projection_t pt where pt.current_projector_id = p.id) as current_projection_ids, +(select array_agg(pt.id ORDER BY pt.id) from projection_t pt where pt.preview_projector_id = p.id) as preview_projection_ids, +(select array_agg(pt.id ORDER BY pt.id) from projection_t pt where pt.history_projector_id = p.id) as history_projection_ids, +(select m.id from meeting_t m where m.reference_projector_id = p.id) as used_as_reference_projector_meeting_id +FROM projector_t p; + + +CREATE VIEW "projector_countdown" AS SELECT *, +(select array_agg(pt.id ORDER BY pt.id) from projection_t pt where pt.content_object_id_projector_countdown_id = p.id) as projection_ids, +(select m.id from meeting_t m where m.list_of_speakers_countdown_id = p.id) as used_as_list_of_speakers_countdown_meeting_id, +(select m.id from meeting_t m where m.poll_countdown_id = p.id) as used_as_poll_countdown_meeting_id +FROM projector_countdown_t p; + + +CREATE VIEW "projector_message" AS SELECT *, +(select array_agg(pt.id ORDER BY pt.id) from projection_t pt where pt.content_object_id_projector_message_id = p.id) as projection_ids +FROM projector_message_t p; + + +CREATE VIEW "speaker" AS SELECT * FROM speaker_t s; + + +CREATE VIEW "structure_level" AS SELECT *, +(select array_agg(n.meeting_user_id ORDER BY n.meeting_user_id) from nm_meeting_user_structure_level_ids_structure_level_t n where n.structure_level_id = s.id) as meeting_user_ids, +(select array_agg(sl.id ORDER BY sl.id) from structure_level_list_of_speakers_t sl where sl.structure_level_id = s.id) as structure_level_list_of_speakers_ids +FROM structure_level_t s; + + +CREATE VIEW "structure_level_list_of_speakers" AS SELECT *, +(select array_agg(st.id ORDER BY st.id) from speaker_t st where st.structure_level_list_of_speakers_id = s.id) as speaker_ids +FROM structure_level_list_of_speakers_t s; + + +CREATE VIEW "tag" AS SELECT *, +(select array_agg(g.tagged_id ORDER BY g.tagged_id) from gm_tag_tagged_ids_t g where g.tag_id = t.id) as tagged_ids +FROM tag_t t; + + +CREATE VIEW "theme" AS SELECT *, +(select o.id from organization_t o where o.theme_id = t.id) as theme_for_organization_id +FROM theme_t t; + + +CREATE VIEW "topic" AS SELECT *, +(select array_agg(g.meeting_mediafile_id ORDER BY g.meeting_mediafile_id) from gm_meeting_mediafile_attachment_ids_t g where g.attachment_id_topic_id = t.id) as attachment_meeting_mediafile_ids, +(select a.id from agenda_item_t a where a.content_object_id_topic_id = t.id) as agenda_item_id, +(select l.id from list_of_speakers_t l where l.content_object_id_topic_id = t.id) as list_of_speakers_id, +(select array_agg(p.id ORDER BY p.id) from poll_t p where p.content_object_id_topic_id = t.id) as poll_ids, +(select array_agg(p.id ORDER BY p.id) from projection_t p where p.content_object_id_topic_id = t.id) as projection_ids +FROM topic_t t; + + +CREATE VIEW "user" AS SELECT *, +(select array_agg(n.meeting_id ORDER BY n.meeting_id) from nm_meeting_present_user_ids_user_t n where n.user_id = u.id) as is_present_in_meeting_ids, +( + SELECT array_agg(DISTINCT ci.committee_id ORDER BY ci.committee_id) + FROM ( + -- Select committee_ids from meetings the user is part of + SELECT m.committee_id + FROM meeting_user_t AS mu + INNER JOIN meeting_t AS m ON m.id = mu.meeting_id + WHERE mu.user_id = u.id + + UNION + + -- Select committee_ids from committee managers + SELECT cmu.committee_id + FROM nm_committee_manager_ids_user_t cmu + WHERE cmu.user_id = u.id + + UNION + + -- Select home_committee_id from user + SELECT u_hc.home_committee_id + FROM user_t u_hc + WHERE u_hc.home_committee_id IS NOT NULL AND u_hc.id = u.id + ) AS ci +) AS committee_ids +, +(select array_agg(n.committee_id ORDER BY n.committee_id) from nm_committee_manager_ids_user_t n where n.user_id = u.id) as committee_management_ids, +(select array_agg(m.id ORDER BY m.id) from meeting_user_t m where m.user_id = u.id) as meeting_user_ids, +(select array_agg(n.poll_id ORDER BY n.poll_id) from nm_poll_voted_ids_user_t n where n.user_id = u.id) as poll_voted_ids, +(select array_agg(o.id ORDER BY o.id) from option_t o where o.content_object_id_user_id = u.id) as option_ids, +(select array_agg(v.id ORDER BY v.id) from vote_t v where v.user_id = u.id) as vote_ids, +(select array_agg(v.id ORDER BY v.id) from vote_t v where v.delegated_user_id = u.id) as delegated_vote_ids, +(select array_agg(p.id ORDER BY p.id) from poll_candidate_t p where p.user_id = u.id) as poll_candidate_ids, +(select array_agg(h.id ORDER BY h.id) from history_position_t h where h.user_id = u.id) as history_position_ids, +(select array_agg(h.id ORDER BY h.id) from history_entry_t h where h.model_id_user_id = u.id) as history_entry_ids, +( + SELECT array_agg(DISTINCT mu.meeting_id ORDER BY mu.meeting_id) + FROM meeting_user_t mu + WHERE mu.user_id = u.id +) AS meeting_ids + +FROM user_t u; + +comment on column "user".committee_ids is 'Calculated field: Returns committee_ids, where the user is manager or member in a meeting'; +comment on column "user".meeting_ids is 'Calculated. All ids from meetings calculated via meeting_user.'; + +CREATE VIEW "vote" AS SELECT * FROM vote_t v; + + + +-- Alter table relations +ALTER TABLE agenda_item_t ADD FOREIGN KEY(content_object_id_motion_id) REFERENCES motion_t(id) INITIALLY DEFERRED; +CREATE INDEX ON agenda_item_t (content_object_id_motion_id); +ALTER TABLE agenda_item_t ADD FOREIGN KEY(content_object_id_motion_block_id) REFERENCES motion_block_t(id) INITIALLY DEFERRED; +CREATE INDEX ON agenda_item_t (content_object_id_motion_block_id); +ALTER TABLE agenda_item_t ADD FOREIGN KEY(content_object_id_assignment_id) REFERENCES assignment_t(id) INITIALLY DEFERRED; +CREATE INDEX ON agenda_item_t (content_object_id_assignment_id); +ALTER TABLE agenda_item_t ADD FOREIGN KEY(content_object_id_topic_id) REFERENCES topic_t(id) INITIALLY DEFERRED; +CREATE INDEX ON agenda_item_t (content_object_id_topic_id); +ALTER TABLE agenda_item_t ADD FOREIGN KEY(parent_id) REFERENCES agenda_item_t(id) INITIALLY DEFERRED; +CREATE INDEX ON agenda_item_t (parent_id); +ALTER TABLE agenda_item_t ADD FOREIGN KEY(meeting_id) REFERENCES meeting_t(id) INITIALLY DEFERRED; +CREATE INDEX ON agenda_item_t (meeting_id); + +ALTER TABLE assignment_t ADD FOREIGN KEY(meeting_id) REFERENCES meeting_t(id) INITIALLY DEFERRED; +CREATE INDEX ON assignment_t (meeting_id); + +ALTER TABLE assignment_candidate_t ADD FOREIGN KEY(assignment_id) REFERENCES assignment_t(id) INITIALLY DEFERRED; +CREATE INDEX ON assignment_candidate_t (assignment_id); +ALTER TABLE assignment_candidate_t ADD FOREIGN KEY(meeting_user_id) REFERENCES meeting_user_t(id) INITIALLY DEFERRED; +CREATE INDEX ON assignment_candidate_t (meeting_user_id); +ALTER TABLE assignment_candidate_t ADD FOREIGN KEY(meeting_id) REFERENCES meeting_t(id) INITIALLY DEFERRED; +CREATE INDEX ON assignment_candidate_t (meeting_id); + +ALTER TABLE chat_group_t ADD FOREIGN KEY(meeting_id) REFERENCES meeting_t(id) INITIALLY DEFERRED; +CREATE INDEX ON chat_group_t (meeting_id); + +ALTER TABLE chat_message_t ADD FOREIGN KEY(meeting_user_id) REFERENCES meeting_user_t(id) INITIALLY DEFERRED; +CREATE INDEX ON chat_message_t (meeting_user_id); +ALTER TABLE chat_message_t ADD FOREIGN KEY(chat_group_id) REFERENCES chat_group_t(id) INITIALLY DEFERRED; +CREATE INDEX ON chat_message_t (chat_group_id); +ALTER TABLE chat_message_t ADD FOREIGN KEY(meeting_id) REFERENCES meeting_t(id) INITIALLY DEFERRED; +CREATE INDEX ON chat_message_t (meeting_id); + +ALTER TABLE committee_t ADD FOREIGN KEY(default_meeting_id) REFERENCES meeting_t(id) INITIALLY DEFERRED; +CREATE INDEX ON committee_t (default_meeting_id); +ALTER TABLE committee_t ADD FOREIGN KEY(parent_id) REFERENCES committee_t(id) INITIALLY DEFERRED; +CREATE INDEX ON committee_t (parent_id); + +ALTER TABLE group_t ADD FOREIGN KEY(used_as_motion_poll_default_id) REFERENCES meeting_t(id) INITIALLY DEFERRED; +CREATE INDEX ON group_t (used_as_motion_poll_default_id); +ALTER TABLE group_t ADD FOREIGN KEY(used_as_assignment_poll_default_id) REFERENCES meeting_t(id) INITIALLY DEFERRED; +CREATE INDEX ON group_t (used_as_assignment_poll_default_id); +ALTER TABLE group_t ADD FOREIGN KEY(used_as_topic_poll_default_id) REFERENCES meeting_t(id) INITIALLY DEFERRED; +CREATE INDEX ON group_t (used_as_topic_poll_default_id); +ALTER TABLE group_t ADD FOREIGN KEY(used_as_poll_default_id) REFERENCES meeting_t(id) INITIALLY DEFERRED; +CREATE INDEX ON group_t (used_as_poll_default_id); +ALTER TABLE group_t ADD FOREIGN KEY(meeting_id) REFERENCES meeting_t(id) INITIALLY DEFERRED; +CREATE INDEX ON group_t (meeting_id); + +ALTER TABLE history_entry_t ADD FOREIGN KEY(model_id_user_id) REFERENCES user_t(id) INITIALLY DEFERRED; +CREATE INDEX ON history_entry_t (model_id_user_id); +ALTER TABLE history_entry_t ADD FOREIGN KEY(model_id_motion_id) REFERENCES motion_t(id) INITIALLY DEFERRED; +CREATE INDEX ON history_entry_t (model_id_motion_id); +ALTER TABLE history_entry_t ADD FOREIGN KEY(model_id_assignment_id) REFERENCES assignment_t(id) INITIALLY DEFERRED; +CREATE INDEX ON history_entry_t (model_id_assignment_id); +ALTER TABLE history_entry_t ADD FOREIGN KEY(position_id) REFERENCES history_position_t(id) INITIALLY DEFERRED; +CREATE INDEX ON history_entry_t (position_id); +ALTER TABLE history_entry_t ADD FOREIGN KEY(meeting_id) REFERENCES meeting_t(id) INITIALLY DEFERRED; +CREATE INDEX ON history_entry_t (meeting_id); + +ALTER TABLE history_position_t ADD FOREIGN KEY(user_id) REFERENCES user_t(id) INITIALLY DEFERRED; +CREATE INDEX ON history_position_t (user_id); + +ALTER TABLE list_of_speakers_t ADD FOREIGN KEY(content_object_id_motion_id) REFERENCES motion_t(id) INITIALLY DEFERRED; +CREATE INDEX ON list_of_speakers_t (content_object_id_motion_id); +ALTER TABLE list_of_speakers_t ADD FOREIGN KEY(content_object_id_motion_block_id) REFERENCES motion_block_t(id) INITIALLY DEFERRED; +CREATE INDEX ON list_of_speakers_t (content_object_id_motion_block_id); +ALTER TABLE list_of_speakers_t ADD FOREIGN KEY(content_object_id_assignment_id) REFERENCES assignment_t(id) INITIALLY DEFERRED; +CREATE INDEX ON list_of_speakers_t (content_object_id_assignment_id); +ALTER TABLE list_of_speakers_t ADD FOREIGN KEY(content_object_id_topic_id) REFERENCES topic_t(id) INITIALLY DEFERRED; +CREATE INDEX ON list_of_speakers_t (content_object_id_topic_id); +ALTER TABLE list_of_speakers_t ADD FOREIGN KEY(content_object_id_meeting_mediafile_id) REFERENCES meeting_mediafile_t(id) INITIALLY DEFERRED; +CREATE INDEX ON list_of_speakers_t (content_object_id_meeting_mediafile_id); +ALTER TABLE list_of_speakers_t ADD FOREIGN KEY(meeting_id) REFERENCES meeting_t(id) INITIALLY DEFERRED; +CREATE INDEX ON list_of_speakers_t (meeting_id); + +ALTER TABLE mediafile_t ADD FOREIGN KEY(published_to_meetings_in_organization_id) REFERENCES organization_t(id) INITIALLY DEFERRED; +CREATE INDEX ON mediafile_t (published_to_meetings_in_organization_id); +ALTER TABLE mediafile_t ADD FOREIGN KEY(parent_id) REFERENCES mediafile_t(id) INITIALLY DEFERRED; +CREATE INDEX ON mediafile_t (parent_id); +ALTER TABLE mediafile_t ADD FOREIGN KEY(owner_id_meeting_id) REFERENCES meeting_t(id) INITIALLY DEFERRED; +CREATE INDEX ON mediafile_t (owner_id_meeting_id); +ALTER TABLE mediafile_t ADD FOREIGN KEY(owner_id_organization_id) REFERENCES organization_t(id) INITIALLY DEFERRED; +CREATE INDEX ON mediafile_t (owner_id_organization_id); + +ALTER TABLE meeting_t ADD FOREIGN KEY(is_active_in_organization_id) REFERENCES organization_t(id) INITIALLY DEFERRED; +CREATE INDEX ON meeting_t (is_active_in_organization_id); +ALTER TABLE meeting_t ADD FOREIGN KEY(is_archived_in_organization_id) REFERENCES organization_t(id) INITIALLY DEFERRED; +CREATE INDEX ON meeting_t (is_archived_in_organization_id); +ALTER TABLE meeting_t ADD FOREIGN KEY(template_for_organization_id) REFERENCES organization_t(id) INITIALLY DEFERRED; +CREATE INDEX ON meeting_t (template_for_organization_id); +ALTER TABLE meeting_t ADD FOREIGN KEY(motions_default_workflow_id) REFERENCES motion_workflow_t(id) INITIALLY DEFERRED; +CREATE INDEX ON meeting_t (motions_default_workflow_id); +ALTER TABLE meeting_t ADD FOREIGN KEY(motions_default_amendment_workflow_id) REFERENCES motion_workflow_t(id) INITIALLY DEFERRED; +CREATE INDEX ON meeting_t (motions_default_amendment_workflow_id); +ALTER TABLE meeting_t ADD FOREIGN KEY(logo_projector_main_id) REFERENCES meeting_mediafile_t(id) INITIALLY DEFERRED; +CREATE INDEX ON meeting_t (logo_projector_main_id); +ALTER TABLE meeting_t ADD FOREIGN KEY(logo_projector_header_id) REFERENCES meeting_mediafile_t(id) INITIALLY DEFERRED; +CREATE INDEX ON meeting_t (logo_projector_header_id); +ALTER TABLE meeting_t ADD FOREIGN KEY(logo_web_header_id) REFERENCES meeting_mediafile_t(id) INITIALLY DEFERRED; +CREATE INDEX ON meeting_t (logo_web_header_id); +ALTER TABLE meeting_t ADD FOREIGN KEY(logo_pdf_header_l_id) REFERENCES meeting_mediafile_t(id) INITIALLY DEFERRED; +CREATE INDEX ON meeting_t (logo_pdf_header_l_id); +ALTER TABLE meeting_t ADD FOREIGN KEY(logo_pdf_header_r_id) REFERENCES meeting_mediafile_t(id) INITIALLY DEFERRED; +CREATE INDEX ON meeting_t (logo_pdf_header_r_id); +ALTER TABLE meeting_t ADD FOREIGN KEY(logo_pdf_footer_l_id) REFERENCES meeting_mediafile_t(id) INITIALLY DEFERRED; +CREATE INDEX ON meeting_t (logo_pdf_footer_l_id); +ALTER TABLE meeting_t ADD FOREIGN KEY(logo_pdf_footer_r_id) REFERENCES meeting_mediafile_t(id) INITIALLY DEFERRED; +CREATE INDEX ON meeting_t (logo_pdf_footer_r_id); +ALTER TABLE meeting_t ADD FOREIGN KEY(logo_pdf_ballot_paper_id) REFERENCES meeting_mediafile_t(id) INITIALLY DEFERRED; +CREATE INDEX ON meeting_t (logo_pdf_ballot_paper_id); +ALTER TABLE meeting_t ADD FOREIGN KEY(font_regular_id) REFERENCES meeting_mediafile_t(id) INITIALLY DEFERRED; +CREATE INDEX ON meeting_t (font_regular_id); +ALTER TABLE meeting_t ADD FOREIGN KEY(font_italic_id) REFERENCES meeting_mediafile_t(id) INITIALLY DEFERRED; +CREATE INDEX ON meeting_t (font_italic_id); +ALTER TABLE meeting_t ADD FOREIGN KEY(font_bold_id) REFERENCES meeting_mediafile_t(id) INITIALLY DEFERRED; +CREATE INDEX ON meeting_t (font_bold_id); +ALTER TABLE meeting_t ADD FOREIGN KEY(font_bold_italic_id) REFERENCES meeting_mediafile_t(id) INITIALLY DEFERRED; +CREATE INDEX ON meeting_t (font_bold_italic_id); +ALTER TABLE meeting_t ADD FOREIGN KEY(font_monospace_id) REFERENCES meeting_mediafile_t(id) INITIALLY DEFERRED; +CREATE INDEX ON meeting_t (font_monospace_id); +ALTER TABLE meeting_t ADD FOREIGN KEY(font_chyron_speaker_name_id) REFERENCES meeting_mediafile_t(id) INITIALLY DEFERRED; +CREATE INDEX ON meeting_t (font_chyron_speaker_name_id); +ALTER TABLE meeting_t ADD FOREIGN KEY(font_projector_h1_id) REFERENCES meeting_mediafile_t(id) INITIALLY DEFERRED; +CREATE INDEX ON meeting_t (font_projector_h1_id); +ALTER TABLE meeting_t ADD FOREIGN KEY(font_projector_h2_id) REFERENCES meeting_mediafile_t(id) INITIALLY DEFERRED; +CREATE INDEX ON meeting_t (font_projector_h2_id); +ALTER TABLE meeting_t ADD FOREIGN KEY(committee_id) REFERENCES committee_t(id) INITIALLY DEFERRED; +CREATE INDEX ON meeting_t (committee_id); +ALTER TABLE meeting_t ADD FOREIGN KEY(reference_projector_id) REFERENCES projector_t(id) INITIALLY DEFERRED; +CREATE INDEX ON meeting_t (reference_projector_id); +ALTER TABLE meeting_t ADD FOREIGN KEY(list_of_speakers_countdown_id) REFERENCES projector_countdown_t(id) INITIALLY DEFERRED; +CREATE INDEX ON meeting_t (list_of_speakers_countdown_id); +ALTER TABLE meeting_t ADD FOREIGN KEY(poll_countdown_id) REFERENCES projector_countdown_t(id) INITIALLY DEFERRED; +CREATE INDEX ON meeting_t (poll_countdown_id); +ALTER TABLE meeting_t ADD FOREIGN KEY(default_group_id) REFERENCES group_t(id) INITIALLY DEFERRED; +CREATE INDEX ON meeting_t (default_group_id); +ALTER TABLE meeting_t ADD FOREIGN KEY(admin_group_id) REFERENCES group_t(id) INITIALLY DEFERRED; +CREATE INDEX ON meeting_t (admin_group_id); +ALTER TABLE meeting_t ADD FOREIGN KEY(anonymous_group_id) REFERENCES group_t(id) INITIALLY DEFERRED; +CREATE INDEX ON meeting_t (anonymous_group_id); + +ALTER TABLE meeting_mediafile_t ADD FOREIGN KEY(mediafile_id) REFERENCES mediafile_t(id) INITIALLY DEFERRED; +CREATE INDEX ON meeting_mediafile_t (mediafile_id); +ALTER TABLE meeting_mediafile_t ADD FOREIGN KEY(meeting_id) REFERENCES meeting_t(id) INITIALLY DEFERRED; +CREATE INDEX ON meeting_mediafile_t (meeting_id); + +ALTER TABLE meeting_user_t ADD FOREIGN KEY(user_id) REFERENCES user_t(id) INITIALLY DEFERRED; +CREATE INDEX ON meeting_user_t (user_id); +ALTER TABLE meeting_user_t ADD FOREIGN KEY(meeting_id) REFERENCES meeting_t(id) INITIALLY DEFERRED; +CREATE INDEX ON meeting_user_t (meeting_id); +ALTER TABLE meeting_user_t ADD FOREIGN KEY(vote_delegated_to_id) REFERENCES meeting_user_t(id) INITIALLY DEFERRED; +CREATE INDEX ON meeting_user_t (vote_delegated_to_id); + +ALTER TABLE motion_t ADD FOREIGN KEY(lead_motion_id) REFERENCES motion_t(id) INITIALLY DEFERRED; +CREATE INDEX ON motion_t (lead_motion_id); +ALTER TABLE motion_t ADD FOREIGN KEY(sort_parent_id) REFERENCES motion_t(id) INITIALLY DEFERRED; +CREATE INDEX ON motion_t (sort_parent_id); +ALTER TABLE motion_t ADD FOREIGN KEY(origin_id) REFERENCES motion_t(id) INITIALLY DEFERRED; +CREATE INDEX ON motion_t (origin_id); +ALTER TABLE motion_t ADD FOREIGN KEY(origin_meeting_id) REFERENCES meeting_t(id) INITIALLY DEFERRED; +CREATE INDEX ON motion_t (origin_meeting_id); +ALTER TABLE motion_t ADD FOREIGN KEY(state_id) REFERENCES motion_state_t(id) INITIALLY DEFERRED; +CREATE INDEX ON motion_t (state_id); +ALTER TABLE motion_t ADD FOREIGN KEY(recommendation_id) REFERENCES motion_state_t(id) INITIALLY DEFERRED; +CREATE INDEX ON motion_t (recommendation_id); +ALTER TABLE motion_t ADD FOREIGN KEY(category_id) REFERENCES motion_category_t(id) INITIALLY DEFERRED; +CREATE INDEX ON motion_t (category_id); +ALTER TABLE motion_t ADD FOREIGN KEY(block_id) REFERENCES motion_block_t(id) INITIALLY DEFERRED; +CREATE INDEX ON motion_t (block_id); +ALTER TABLE motion_t ADD FOREIGN KEY(meeting_id) REFERENCES meeting_t(id) INITIALLY DEFERRED; +CREATE INDEX ON motion_t (meeting_id); + +ALTER TABLE motion_block_t ADD FOREIGN KEY(meeting_id) REFERENCES meeting_t(id) INITIALLY DEFERRED; +CREATE INDEX ON motion_block_t (meeting_id); + +ALTER TABLE motion_category_t ADD FOREIGN KEY(parent_id) REFERENCES motion_category_t(id) INITIALLY DEFERRED; +CREATE INDEX ON motion_category_t (parent_id); +ALTER TABLE motion_category_t ADD FOREIGN KEY(meeting_id) REFERENCES meeting_t(id) INITIALLY DEFERRED; +CREATE INDEX ON motion_category_t (meeting_id); + +ALTER TABLE motion_change_recommendation_t ADD FOREIGN KEY(motion_id) REFERENCES motion_t(id) INITIALLY DEFERRED; +CREATE INDEX ON motion_change_recommendation_t (motion_id); +ALTER TABLE motion_change_recommendation_t ADD FOREIGN KEY(meeting_id) REFERENCES meeting_t(id) INITIALLY DEFERRED; +CREATE INDEX ON motion_change_recommendation_t (meeting_id); + +ALTER TABLE motion_comment_t ADD FOREIGN KEY(motion_id) REFERENCES motion_t(id) INITIALLY DEFERRED; +CREATE INDEX ON motion_comment_t (motion_id); +ALTER TABLE motion_comment_t ADD FOREIGN KEY(section_id) REFERENCES motion_comment_section_t(id) INITIALLY DEFERRED; +CREATE INDEX ON motion_comment_t (section_id); +ALTER TABLE motion_comment_t ADD FOREIGN KEY(meeting_id) REFERENCES meeting_t(id) INITIALLY DEFERRED; +CREATE INDEX ON motion_comment_t (meeting_id); + +ALTER TABLE motion_comment_section_t ADD FOREIGN KEY(meeting_id) REFERENCES meeting_t(id) INITIALLY DEFERRED; +CREATE INDEX ON motion_comment_section_t (meeting_id); + +ALTER TABLE motion_editor_t ADD FOREIGN KEY(meeting_user_id) REFERENCES meeting_user_t(id) INITIALLY DEFERRED; +CREATE INDEX ON motion_editor_t (meeting_user_id); +ALTER TABLE motion_editor_t ADD FOREIGN KEY(motion_id) REFERENCES motion_t(id) INITIALLY DEFERRED; +CREATE INDEX ON motion_editor_t (motion_id); +ALTER TABLE motion_editor_t ADD FOREIGN KEY(meeting_id) REFERENCES meeting_t(id) INITIALLY DEFERRED; +CREATE INDEX ON motion_editor_t (meeting_id); + +ALTER TABLE motion_state_t ADD FOREIGN KEY(submitter_withdraw_state_id) REFERENCES motion_state_t(id) INITIALLY DEFERRED; +CREATE INDEX ON motion_state_t (submitter_withdraw_state_id); +ALTER TABLE motion_state_t ADD FOREIGN KEY(workflow_id) REFERENCES motion_workflow_t(id) INITIALLY DEFERRED; +CREATE INDEX ON motion_state_t (workflow_id); +ALTER TABLE motion_state_t ADD FOREIGN KEY(meeting_id) REFERENCES meeting_t(id) INITIALLY DEFERRED; +CREATE INDEX ON motion_state_t (meeting_id); + +ALTER TABLE motion_submitter_t ADD FOREIGN KEY(meeting_user_id) REFERENCES meeting_user_t(id) INITIALLY DEFERRED; +CREATE INDEX ON motion_submitter_t (meeting_user_id); +ALTER TABLE motion_submitter_t ADD FOREIGN KEY(motion_id) REFERENCES motion_t(id) INITIALLY DEFERRED; +CREATE INDEX ON motion_submitter_t (motion_id); +ALTER TABLE motion_submitter_t ADD FOREIGN KEY(meeting_id) REFERENCES meeting_t(id) INITIALLY DEFERRED; +CREATE INDEX ON motion_submitter_t (meeting_id); + +ALTER TABLE motion_supporter_t ADD FOREIGN KEY(meeting_user_id) REFERENCES meeting_user_t(id) INITIALLY DEFERRED; +CREATE INDEX ON motion_supporter_t (meeting_user_id); +ALTER TABLE motion_supporter_t ADD FOREIGN KEY(motion_id) REFERENCES motion_t(id) INITIALLY DEFERRED; +CREATE INDEX ON motion_supporter_t (motion_id); +ALTER TABLE motion_supporter_t ADD FOREIGN KEY(meeting_id) REFERENCES meeting_t(id) INITIALLY DEFERRED; +CREATE INDEX ON motion_supporter_t (meeting_id); + +ALTER TABLE motion_workflow_t ADD FOREIGN KEY(first_state_id) REFERENCES motion_state_t(id) INITIALLY DEFERRED; +CREATE INDEX ON motion_workflow_t (first_state_id); +ALTER TABLE motion_workflow_t ADD FOREIGN KEY(meeting_id) REFERENCES meeting_t(id) INITIALLY DEFERRED; +CREATE INDEX ON motion_workflow_t (meeting_id); + +ALTER TABLE motion_working_group_speaker_t ADD FOREIGN KEY(meeting_user_id) REFERENCES meeting_user_t(id) INITIALLY DEFERRED; +CREATE INDEX ON motion_working_group_speaker_t (meeting_user_id); +ALTER TABLE motion_working_group_speaker_t ADD FOREIGN KEY(motion_id) REFERENCES motion_t(id) INITIALLY DEFERRED; +CREATE INDEX ON motion_working_group_speaker_t (motion_id); +ALTER TABLE motion_working_group_speaker_t ADD FOREIGN KEY(meeting_id) REFERENCES meeting_t(id) INITIALLY DEFERRED; +CREATE INDEX ON motion_working_group_speaker_t (meeting_id); + +ALTER TABLE option_t ADD FOREIGN KEY(poll_id) REFERENCES poll_t(id) INITIALLY DEFERRED; +CREATE INDEX ON option_t (poll_id); +ALTER TABLE option_t ADD FOREIGN KEY(used_as_global_option_in_poll_id) REFERENCES poll_t(id) INITIALLY DEFERRED; +CREATE INDEX ON option_t (used_as_global_option_in_poll_id); +ALTER TABLE option_t ADD FOREIGN KEY(content_object_id_motion_id) REFERENCES motion_t(id) INITIALLY DEFERRED; +CREATE INDEX ON option_t (content_object_id_motion_id); +ALTER TABLE option_t ADD FOREIGN KEY(content_object_id_user_id) REFERENCES user_t(id) INITIALLY DEFERRED; +CREATE INDEX ON option_t (content_object_id_user_id); +ALTER TABLE option_t ADD FOREIGN KEY(content_object_id_poll_candidate_list_id) REFERENCES poll_candidate_list_t(id) INITIALLY DEFERRED; +CREATE INDEX ON option_t (content_object_id_poll_candidate_list_id); +ALTER TABLE option_t ADD FOREIGN KEY(meeting_id) REFERENCES meeting_t(id) INITIALLY DEFERRED; +CREATE INDEX ON option_t (meeting_id); + +ALTER TABLE organization_t ADD FOREIGN KEY(theme_id) REFERENCES theme_t(id) INITIALLY DEFERRED; +CREATE INDEX ON organization_t (theme_id); + +ALTER TABLE personal_note_t ADD FOREIGN KEY(meeting_user_id) REFERENCES meeting_user_t(id) INITIALLY DEFERRED; +CREATE INDEX ON personal_note_t (meeting_user_id); +ALTER TABLE personal_note_t ADD FOREIGN KEY(content_object_id_motion_id) REFERENCES motion_t(id) INITIALLY DEFERRED; +CREATE INDEX ON personal_note_t (content_object_id_motion_id); +ALTER TABLE personal_note_t ADD FOREIGN KEY(meeting_id) REFERENCES meeting_t(id) INITIALLY DEFERRED; +CREATE INDEX ON personal_note_t (meeting_id); + +ALTER TABLE point_of_order_category_t ADD FOREIGN KEY(meeting_id) REFERENCES meeting_t(id) INITIALLY DEFERRED; +CREATE INDEX ON point_of_order_category_t (meeting_id); + +ALTER TABLE poll_t ADD FOREIGN KEY(content_object_id_motion_id) REFERENCES motion_t(id) INITIALLY DEFERRED; +CREATE INDEX ON poll_t (content_object_id_motion_id); +ALTER TABLE poll_t ADD FOREIGN KEY(content_object_id_assignment_id) REFERENCES assignment_t(id) INITIALLY DEFERRED; +CREATE INDEX ON poll_t (content_object_id_assignment_id); +ALTER TABLE poll_t ADD FOREIGN KEY(content_object_id_topic_id) REFERENCES topic_t(id) INITIALLY DEFERRED; +CREATE INDEX ON poll_t (content_object_id_topic_id); +ALTER TABLE poll_t ADD FOREIGN KEY(global_option_id) REFERENCES option_t(id) INITIALLY DEFERRED; +CREATE INDEX ON poll_t (global_option_id); +ALTER TABLE poll_t ADD FOREIGN KEY(meeting_id) REFERENCES meeting_t(id) INITIALLY DEFERRED; +CREATE INDEX ON poll_t (meeting_id); + +ALTER TABLE poll_candidate_t ADD FOREIGN KEY(poll_candidate_list_id) REFERENCES poll_candidate_list_t(id) INITIALLY DEFERRED; +CREATE INDEX ON poll_candidate_t (poll_candidate_list_id); +ALTER TABLE poll_candidate_t ADD FOREIGN KEY(user_id) REFERENCES user_t(id) INITIALLY DEFERRED; +CREATE INDEX ON poll_candidate_t (user_id); +ALTER TABLE poll_candidate_t ADD FOREIGN KEY(meeting_id) REFERENCES meeting_t(id) INITIALLY DEFERRED; +CREATE INDEX ON poll_candidate_t (meeting_id); + +ALTER TABLE poll_candidate_list_t ADD FOREIGN KEY(meeting_id) REFERENCES meeting_t(id) INITIALLY DEFERRED; +CREATE INDEX ON poll_candidate_list_t (meeting_id); + +ALTER TABLE projection_t ADD FOREIGN KEY(current_projector_id) REFERENCES projector_t(id) INITIALLY DEFERRED; +CREATE INDEX ON projection_t (current_projector_id); +ALTER TABLE projection_t ADD FOREIGN KEY(preview_projector_id) REFERENCES projector_t(id) INITIALLY DEFERRED; +CREATE INDEX ON projection_t (preview_projector_id); +ALTER TABLE projection_t ADD FOREIGN KEY(history_projector_id) REFERENCES projector_t(id) INITIALLY DEFERRED; +CREATE INDEX ON projection_t (history_projector_id); +ALTER TABLE projection_t ADD FOREIGN KEY(content_object_id_meeting_id) REFERENCES meeting_t(id) INITIALLY DEFERRED; +CREATE INDEX ON projection_t (content_object_id_meeting_id); +ALTER TABLE projection_t ADD FOREIGN KEY(content_object_id_motion_id) REFERENCES motion_t(id) INITIALLY DEFERRED; +CREATE INDEX ON projection_t (content_object_id_motion_id); +ALTER TABLE projection_t ADD FOREIGN KEY(content_object_id_meeting_mediafile_id) REFERENCES meeting_mediafile_t(id) INITIALLY DEFERRED; +CREATE INDEX ON projection_t (content_object_id_meeting_mediafile_id); +ALTER TABLE projection_t ADD FOREIGN KEY(content_object_id_list_of_speakers_id) REFERENCES list_of_speakers_t(id) INITIALLY DEFERRED; +CREATE INDEX ON projection_t (content_object_id_list_of_speakers_id); +ALTER TABLE projection_t ADD FOREIGN KEY(content_object_id_motion_block_id) REFERENCES motion_block_t(id) INITIALLY DEFERRED; +CREATE INDEX ON projection_t (content_object_id_motion_block_id); +ALTER TABLE projection_t ADD FOREIGN KEY(content_object_id_assignment_id) REFERENCES assignment_t(id) INITIALLY DEFERRED; +CREATE INDEX ON projection_t (content_object_id_assignment_id); +ALTER TABLE projection_t ADD FOREIGN KEY(content_object_id_agenda_item_id) REFERENCES agenda_item_t(id) INITIALLY DEFERRED; +CREATE INDEX ON projection_t (content_object_id_agenda_item_id); +ALTER TABLE projection_t ADD FOREIGN KEY(content_object_id_topic_id) REFERENCES topic_t(id) INITIALLY DEFERRED; +CREATE INDEX ON projection_t (content_object_id_topic_id); +ALTER TABLE projection_t ADD FOREIGN KEY(content_object_id_poll_id) REFERENCES poll_t(id) INITIALLY DEFERRED; +CREATE INDEX ON projection_t (content_object_id_poll_id); +ALTER TABLE projection_t ADD FOREIGN KEY(content_object_id_projector_message_id) REFERENCES projector_message_t(id) INITIALLY DEFERRED; +CREATE INDEX ON projection_t (content_object_id_projector_message_id); +ALTER TABLE projection_t ADD FOREIGN KEY(content_object_id_projector_countdown_id) REFERENCES projector_countdown_t(id) INITIALLY DEFERRED; +CREATE INDEX ON projection_t (content_object_id_projector_countdown_id); +ALTER TABLE projection_t ADD FOREIGN KEY(meeting_id) REFERENCES meeting_t(id) INITIALLY DEFERRED; +CREATE INDEX ON projection_t (meeting_id); + +ALTER TABLE projector_t ADD FOREIGN KEY(used_as_default_projector_for_agenda_item_list_in_meeting_id) REFERENCES meeting_t(id) INITIALLY DEFERRED; +CREATE INDEX ON projector_t (used_as_default_projector_for_agenda_item_list_in_meeting_id); +ALTER TABLE projector_t ADD FOREIGN KEY(used_as_default_projector_for_topic_in_meeting_id) REFERENCES meeting_t(id) INITIALLY DEFERRED; +CREATE INDEX ON projector_t (used_as_default_projector_for_topic_in_meeting_id); +ALTER TABLE projector_t ADD FOREIGN KEY(used_as_default_projector_for_list_of_speakers_in_meeting_id) REFERENCES meeting_t(id) INITIALLY DEFERRED; +CREATE INDEX ON projector_t (used_as_default_projector_for_list_of_speakers_in_meeting_id); +ALTER TABLE projector_t ADD FOREIGN KEY(used_as_default_projector_for_current_los_in_meeting_id) REFERENCES meeting_t(id) INITIALLY DEFERRED; +CREATE INDEX ON projector_t (used_as_default_projector_for_current_los_in_meeting_id); +ALTER TABLE projector_t ADD FOREIGN KEY(used_as_default_projector_for_motion_in_meeting_id) REFERENCES meeting_t(id) INITIALLY DEFERRED; +CREATE INDEX ON projector_t (used_as_default_projector_for_motion_in_meeting_id); +ALTER TABLE projector_t ADD FOREIGN KEY(used_as_default_projector_for_amendment_in_meeting_id) REFERENCES meeting_t(id) INITIALLY DEFERRED; +CREATE INDEX ON projector_t (used_as_default_projector_for_amendment_in_meeting_id); +ALTER TABLE projector_t ADD FOREIGN KEY(used_as_default_projector_for_motion_block_in_meeting_id) REFERENCES meeting_t(id) INITIALLY DEFERRED; +CREATE INDEX ON projector_t (used_as_default_projector_for_motion_block_in_meeting_id); +ALTER TABLE projector_t ADD FOREIGN KEY(used_as_default_projector_for_assignment_in_meeting_id) REFERENCES meeting_t(id) INITIALLY DEFERRED; +CREATE INDEX ON projector_t (used_as_default_projector_for_assignment_in_meeting_id); +ALTER TABLE projector_t ADD FOREIGN KEY(used_as_default_projector_for_mediafile_in_meeting_id) REFERENCES meeting_t(id) INITIALLY DEFERRED; +CREATE INDEX ON projector_t (used_as_default_projector_for_mediafile_in_meeting_id); +ALTER TABLE projector_t ADD FOREIGN KEY(used_as_default_projector_for_message_in_meeting_id) REFERENCES meeting_t(id) INITIALLY DEFERRED; +CREATE INDEX ON projector_t (used_as_default_projector_for_message_in_meeting_id); +ALTER TABLE projector_t ADD FOREIGN KEY(used_as_default_projector_for_countdown_in_meeting_id) REFERENCES meeting_t(id) INITIALLY DEFERRED; +CREATE INDEX ON projector_t (used_as_default_projector_for_countdown_in_meeting_id); +ALTER TABLE projector_t ADD FOREIGN KEY(used_as_default_projector_for_assignment_poll_in_meeting_id) REFERENCES meeting_t(id) INITIALLY DEFERRED; +CREATE INDEX ON projector_t (used_as_default_projector_for_assignment_poll_in_meeting_id); +ALTER TABLE projector_t ADD FOREIGN KEY(used_as_default_projector_for_motion_poll_in_meeting_id) REFERENCES meeting_t(id) INITIALLY DEFERRED; +CREATE INDEX ON projector_t (used_as_default_projector_for_motion_poll_in_meeting_id); +ALTER TABLE projector_t ADD FOREIGN KEY(used_as_default_projector_for_poll_in_meeting_id) REFERENCES meeting_t(id) INITIALLY DEFERRED; +CREATE INDEX ON projector_t (used_as_default_projector_for_poll_in_meeting_id); +ALTER TABLE projector_t ADD FOREIGN KEY(meeting_id) REFERENCES meeting_t(id) INITIALLY DEFERRED; +CREATE INDEX ON projector_t (meeting_id); + +ALTER TABLE projector_countdown_t ADD FOREIGN KEY(meeting_id) REFERENCES meeting_t(id) INITIALLY DEFERRED; +CREATE INDEX ON projector_countdown_t (meeting_id); + +ALTER TABLE projector_message_t ADD FOREIGN KEY(meeting_id) REFERENCES meeting_t(id) INITIALLY DEFERRED; +CREATE INDEX ON projector_message_t (meeting_id); + +ALTER TABLE speaker_t ADD FOREIGN KEY(list_of_speakers_id) REFERENCES list_of_speakers_t(id) INITIALLY DEFERRED; +CREATE INDEX ON speaker_t (list_of_speakers_id); +ALTER TABLE speaker_t ADD FOREIGN KEY(structure_level_list_of_speakers_id) REFERENCES structure_level_list_of_speakers_t(id) INITIALLY DEFERRED; +CREATE INDEX ON speaker_t (structure_level_list_of_speakers_id); +ALTER TABLE speaker_t ADD FOREIGN KEY(meeting_user_id) REFERENCES meeting_user_t(id) INITIALLY DEFERRED; +CREATE INDEX ON speaker_t (meeting_user_id); +ALTER TABLE speaker_t ADD FOREIGN KEY(point_of_order_category_id) REFERENCES point_of_order_category_t(id) INITIALLY DEFERRED; +CREATE INDEX ON speaker_t (point_of_order_category_id); +ALTER TABLE speaker_t ADD FOREIGN KEY(meeting_id) REFERENCES meeting_t(id) INITIALLY DEFERRED; +CREATE INDEX ON speaker_t (meeting_id); + +ALTER TABLE structure_level_t ADD FOREIGN KEY(meeting_id) REFERENCES meeting_t(id) INITIALLY DEFERRED; +CREATE INDEX ON structure_level_t (meeting_id); + +ALTER TABLE structure_level_list_of_speakers_t ADD FOREIGN KEY(structure_level_id) REFERENCES structure_level_t(id) INITIALLY DEFERRED; +CREATE INDEX ON structure_level_list_of_speakers_t (structure_level_id); +ALTER TABLE structure_level_list_of_speakers_t ADD FOREIGN KEY(list_of_speakers_id) REFERENCES list_of_speakers_t(id) INITIALLY DEFERRED; +CREATE INDEX ON structure_level_list_of_speakers_t (list_of_speakers_id); +ALTER TABLE structure_level_list_of_speakers_t ADD FOREIGN KEY(meeting_id) REFERENCES meeting_t(id) INITIALLY DEFERRED; +CREATE INDEX ON structure_level_list_of_speakers_t (meeting_id); + +ALTER TABLE tag_t ADD FOREIGN KEY(meeting_id) REFERENCES meeting_t(id) INITIALLY DEFERRED; +CREATE INDEX ON tag_t (meeting_id); + +ALTER TABLE topic_t ADD FOREIGN KEY(meeting_id) REFERENCES meeting_t(id) INITIALLY DEFERRED; +CREATE INDEX ON topic_t (meeting_id); + +ALTER TABLE user_t ADD FOREIGN KEY(gender_id) REFERENCES gender_t(id) INITIALLY DEFERRED; +CREATE INDEX ON user_t (gender_id); +ALTER TABLE user_t ADD FOREIGN KEY(home_committee_id) REFERENCES committee_t(id) INITIALLY DEFERRED; +CREATE INDEX ON user_t (home_committee_id); + +ALTER TABLE vote_t ADD FOREIGN KEY(option_id) REFERENCES option_t(id) INITIALLY DEFERRED; +CREATE INDEX ON vote_t (option_id); +ALTER TABLE vote_t ADD FOREIGN KEY(user_id) REFERENCES user_t(id) INITIALLY DEFERRED; +CREATE INDEX ON vote_t (user_id); +ALTER TABLE vote_t ADD FOREIGN KEY(delegated_user_id) REFERENCES user_t(id) INITIALLY DEFERRED; +CREATE INDEX ON vote_t (delegated_user_id); +ALTER TABLE vote_t ADD FOREIGN KEY(meeting_id) REFERENCES meeting_t(id) INITIALLY DEFERRED; +CREATE INDEX ON vote_t (meeting_id); + + + +-- Create triggers generating partitioned sequences + +-- definition trigger generate partitioned sequence number for assignment_t.sequential_number partitioned by meeting_id +CREATE TRIGGER tr_generate_sequence_assignment_sequential_number BEFORE INSERT ON assignment_t +FOR EACH ROW EXECUTE FUNCTION generate_sequence('assignment_t', 'sequential_number', 'meeting_id'); + + +-- definition trigger generate partitioned sequence number for list_of_speakers_t.sequential_number partitioned by meeting_id +CREATE TRIGGER tr_generate_sequence_list_of_speakers_sequential_number BEFORE INSERT ON list_of_speakers_t +FOR EACH ROW EXECUTE FUNCTION generate_sequence('list_of_speakers_t', 'sequential_number', 'meeting_id'); + + +-- definition trigger generate partitioned sequence number for motion_t.sequential_number partitioned by meeting_id +CREATE TRIGGER tr_generate_sequence_motion_sequential_number BEFORE INSERT ON motion_t +FOR EACH ROW EXECUTE FUNCTION generate_sequence('motion_t', 'sequential_number', 'meeting_id'); + + +-- definition trigger generate partitioned sequence number for motion_block_t.sequential_number partitioned by meeting_id +CREATE TRIGGER tr_generate_sequence_motion_block_sequential_number BEFORE INSERT ON motion_block_t +FOR EACH ROW EXECUTE FUNCTION generate_sequence('motion_block_t', 'sequential_number', 'meeting_id'); + + +-- definition trigger generate partitioned sequence number for motion_category_t.sequential_number partitioned by meeting_id +CREATE TRIGGER tr_generate_sequence_motion_category_sequential_number BEFORE INSERT ON motion_category_t +FOR EACH ROW EXECUTE FUNCTION generate_sequence('motion_category_t', 'sequential_number', 'meeting_id'); + + +-- definition trigger generate partitioned sequence number for motion_comment_section_t.sequential_number partitioned by meeting_id +CREATE TRIGGER tr_generate_sequence_motion_comment_section_sequential_number BEFORE INSERT ON motion_comment_section_t +FOR EACH ROW EXECUTE FUNCTION generate_sequence('motion_comment_section_t', 'sequential_number', 'meeting_id'); + + +-- definition trigger generate partitioned sequence number for motion_workflow_t.sequential_number partitioned by meeting_id +CREATE TRIGGER tr_generate_sequence_motion_workflow_sequential_number BEFORE INSERT ON motion_workflow_t +FOR EACH ROW EXECUTE FUNCTION generate_sequence('motion_workflow_t', 'sequential_number', 'meeting_id'); + + +-- definition trigger generate partitioned sequence number for poll_t.sequential_number partitioned by meeting_id +CREATE TRIGGER tr_generate_sequence_poll_sequential_number BEFORE INSERT ON poll_t +FOR EACH ROW EXECUTE FUNCTION generate_sequence('poll_t', 'sequential_number', 'meeting_id'); + + +-- definition trigger generate partitioned sequence number for projector_t.sequential_number partitioned by meeting_id +CREATE TRIGGER tr_generate_sequence_projector_sequential_number BEFORE INSERT ON projector_t +FOR EACH ROW EXECUTE FUNCTION generate_sequence('projector_t', 'sequential_number', 'meeting_id'); + + +-- definition trigger generate partitioned sequence number for topic_t.sequential_number partitioned by meeting_id +CREATE TRIGGER tr_generate_sequence_topic_sequential_number BEFORE INSERT ON topic_t +FOR EACH ROW EXECUTE FUNCTION generate_sequence('topic_t', 'sequential_number', 'meeting_id'); + + + +-- Create triggers checking foreign_id not null for view-relations and no duplicates in 1:1 relationships + +-- definition trigger not null for assignment.list_of_speakers_id against list_of_speakers.content_object_id_assignment_id +CREATE CONSTRAINT TRIGGER tr_i_assignment_list_of_speakers_id AFTER INSERT ON assignment_t INITIALLY DEFERRED +FOR EACH ROW EXECUTE FUNCTION check_not_null_for_1_1('assignment', 'list_of_speakers_id'); + +CREATE CONSTRAINT TRIGGER tr_ud_assignment_list_of_speakers_id AFTER UPDATE OF content_object_id_assignment_id OR DELETE ON list_of_speakers_t INITIALLY DEFERRED +FOR EACH ROW EXECUTE FUNCTION check_not_null_for_1_1('assignment', 'list_of_speakers_id', 'list_of_speakers', 'content_object_id_assignment_id'); + + +-- definition trigger not null for motion.list_of_speakers_id against list_of_speakers.content_object_id_motion_id +CREATE CONSTRAINT TRIGGER tr_i_motion_list_of_speakers_id AFTER INSERT ON motion_t INITIALLY DEFERRED +FOR EACH ROW EXECUTE FUNCTION check_not_null_for_1_1('motion', 'list_of_speakers_id'); + +CREATE CONSTRAINT TRIGGER tr_ud_motion_list_of_speakers_id AFTER UPDATE OF content_object_id_motion_id OR DELETE ON list_of_speakers_t INITIALLY DEFERRED +FOR EACH ROW EXECUTE FUNCTION check_not_null_for_1_1('motion', 'list_of_speakers_id', 'list_of_speakers', 'content_object_id_motion_id'); + + +-- definition trigger not null for motion_block.list_of_speakers_id against list_of_speakers.content_object_id_motion_block_id +CREATE CONSTRAINT TRIGGER tr_i_motion_block_list_of_speakers_id AFTER INSERT ON motion_block_t INITIALLY DEFERRED +FOR EACH ROW EXECUTE FUNCTION check_not_null_for_1_1('motion_block', 'list_of_speakers_id'); + +CREATE CONSTRAINT TRIGGER tr_ud_motion_block_list_of_speakers_id AFTER UPDATE OF content_object_id_motion_block_id OR DELETE ON list_of_speakers_t INITIALLY DEFERRED +FOR EACH ROW EXECUTE FUNCTION check_not_null_for_1_1('motion_block', 'list_of_speakers_id', 'list_of_speakers', 'content_object_id_motion_block_id'); + + +-- definition trigger not null for poll_candidate_list.option_id against option.content_object_id_poll_candidate_list_id +CREATE CONSTRAINT TRIGGER tr_i_poll_candidate_list_option_id AFTER INSERT ON poll_candidate_list_t INITIALLY DEFERRED +FOR EACH ROW EXECUTE FUNCTION check_not_null_for_1_1('poll_candidate_list', 'option_id'); + +CREATE CONSTRAINT TRIGGER tr_ud_poll_candidate_list_option_id AFTER UPDATE OF content_object_id_poll_candidate_list_id OR DELETE ON option_t INITIALLY DEFERRED +FOR EACH ROW EXECUTE FUNCTION check_not_null_for_1_1('poll_candidate_list', 'option_id', 'option', 'content_object_id_poll_candidate_list_id'); + + +-- definition trigger not null for topic.agenda_item_id against agenda_item.content_object_id_topic_id +CREATE CONSTRAINT TRIGGER tr_i_topic_agenda_item_id AFTER INSERT ON topic_t INITIALLY DEFERRED +FOR EACH ROW EXECUTE FUNCTION check_not_null_for_1_1('topic', 'agenda_item_id'); + +CREATE CONSTRAINT TRIGGER tr_ud_topic_agenda_item_id AFTER UPDATE OF content_object_id_topic_id OR DELETE ON agenda_item_t INITIALLY DEFERRED +FOR EACH ROW EXECUTE FUNCTION check_not_null_for_1_1('topic', 'agenda_item_id', 'agenda_item', 'content_object_id_topic_id'); + +-- definition trigger not null for topic.list_of_speakers_id against list_of_speakers.content_object_id_topic_id +CREATE CONSTRAINT TRIGGER tr_i_topic_list_of_speakers_id AFTER INSERT ON topic_t INITIALLY DEFERRED +FOR EACH ROW EXECUTE FUNCTION check_not_null_for_1_1('topic', 'list_of_speakers_id'); + +CREATE CONSTRAINT TRIGGER tr_ud_topic_list_of_speakers_id AFTER UPDATE OF content_object_id_topic_id OR DELETE ON list_of_speakers_t INITIALLY DEFERRED +FOR EACH ROW EXECUTE FUNCTION check_not_null_for_1_1('topic', 'list_of_speakers_id', 'list_of_speakers', 'content_object_id_topic_id'); + + + +-- Create triggers checking foreign_id not null for 1:n relationships + +-- definition trigger not null for meeting.default_projector_agenda_item_list_ids against projector.used_as_default_projector_for_agenda_item_list_in_meeting_id +CREATE CONSTRAINT TRIGGER tr_i_meeting_default_projector_agenda_item_list_ids AFTER INSERT ON meeting_t INITIALLY DEFERRED +FOR EACH ROW EXECUTE FUNCTION check_not_null_for_1_n('meeting_t', 'default_projector_agenda_item_list_ids', 'projector_t', 'used_as_default_projector_for_agenda_item_list_in_meeting_id'); + +CREATE CONSTRAINT TRIGGER tr_ud_meeting_default_projector_agenda_item_list_ids AFTER UPDATE OF used_as_default_projector_for_agenda_item_list_in_meeting_id OR DELETE ON projector_t INITIALLY DEFERRED +FOR EACH ROW EXECUTE FUNCTION check_not_null_for_1_n('meeting_t', 'default_projector_agenda_item_list_ids', 'projector_t', 'used_as_default_projector_for_agenda_item_list_in_meeting_id'); + + +-- definition trigger not null for meeting.default_projector_topic_ids against projector.used_as_default_projector_for_topic_in_meeting_id +CREATE CONSTRAINT TRIGGER tr_i_meeting_default_projector_topic_ids AFTER INSERT ON meeting_t INITIALLY DEFERRED +FOR EACH ROW EXECUTE FUNCTION check_not_null_for_1_n('meeting_t', 'default_projector_topic_ids', 'projector_t', 'used_as_default_projector_for_topic_in_meeting_id'); + +CREATE CONSTRAINT TRIGGER tr_ud_meeting_default_projector_topic_ids AFTER UPDATE OF used_as_default_projector_for_topic_in_meeting_id OR DELETE ON projector_t INITIALLY DEFERRED +FOR EACH ROW EXECUTE FUNCTION check_not_null_for_1_n('meeting_t', 'default_projector_topic_ids', 'projector_t', 'used_as_default_projector_for_topic_in_meeting_id'); + + +-- definition trigger not null for meeting.default_projector_list_of_speakers_ids against projector.used_as_default_projector_for_list_of_speakers_in_meeting_id +CREATE CONSTRAINT TRIGGER tr_i_meeting_default_projector_list_of_speakers_ids AFTER INSERT ON meeting_t INITIALLY DEFERRED +FOR EACH ROW EXECUTE FUNCTION check_not_null_for_1_n('meeting_t', 'default_projector_list_of_speakers_ids', 'projector_t', 'used_as_default_projector_for_list_of_speakers_in_meeting_id'); + +CREATE CONSTRAINT TRIGGER tr_ud_meeting_default_projector_list_of_speakers_ids AFTER UPDATE OF used_as_default_projector_for_list_of_speakers_in_meeting_id OR DELETE ON projector_t INITIALLY DEFERRED +FOR EACH ROW EXECUTE FUNCTION check_not_null_for_1_n('meeting_t', 'default_projector_list_of_speakers_ids', 'projector_t', 'used_as_default_projector_for_list_of_speakers_in_meeting_id'); + + +-- definition trigger not null for meeting.default_projector_current_los_ids against projector.used_as_default_projector_for_current_los_in_meeting_id +CREATE CONSTRAINT TRIGGER tr_i_meeting_default_projector_current_los_ids AFTER INSERT ON meeting_t INITIALLY DEFERRED +FOR EACH ROW EXECUTE FUNCTION check_not_null_for_1_n('meeting_t', 'default_projector_current_los_ids', 'projector_t', 'used_as_default_projector_for_current_los_in_meeting_id'); + +CREATE CONSTRAINT TRIGGER tr_ud_meeting_default_projector_current_los_ids AFTER UPDATE OF used_as_default_projector_for_current_los_in_meeting_id OR DELETE ON projector_t INITIALLY DEFERRED +FOR EACH ROW EXECUTE FUNCTION check_not_null_for_1_n('meeting_t', 'default_projector_current_los_ids', 'projector_t', 'used_as_default_projector_for_current_los_in_meeting_id'); + + +-- definition trigger not null for meeting.default_projector_motion_ids against projector.used_as_default_projector_for_motion_in_meeting_id +CREATE CONSTRAINT TRIGGER tr_i_meeting_default_projector_motion_ids AFTER INSERT ON meeting_t INITIALLY DEFERRED +FOR EACH ROW EXECUTE FUNCTION check_not_null_for_1_n('meeting_t', 'default_projector_motion_ids', 'projector_t', 'used_as_default_projector_for_motion_in_meeting_id'); + +CREATE CONSTRAINT TRIGGER tr_ud_meeting_default_projector_motion_ids AFTER UPDATE OF used_as_default_projector_for_motion_in_meeting_id OR DELETE ON projector_t INITIALLY DEFERRED +FOR EACH ROW EXECUTE FUNCTION check_not_null_for_1_n('meeting_t', 'default_projector_motion_ids', 'projector_t', 'used_as_default_projector_for_motion_in_meeting_id'); + + +-- definition trigger not null for meeting.default_projector_amendment_ids against projector.used_as_default_projector_for_amendment_in_meeting_id +CREATE CONSTRAINT TRIGGER tr_i_meeting_default_projector_amendment_ids AFTER INSERT ON meeting_t INITIALLY DEFERRED +FOR EACH ROW EXECUTE FUNCTION check_not_null_for_1_n('meeting_t', 'default_projector_amendment_ids', 'projector_t', 'used_as_default_projector_for_amendment_in_meeting_id'); + +CREATE CONSTRAINT TRIGGER tr_ud_meeting_default_projector_amendment_ids AFTER UPDATE OF used_as_default_projector_for_amendment_in_meeting_id OR DELETE ON projector_t INITIALLY DEFERRED +FOR EACH ROW EXECUTE FUNCTION check_not_null_for_1_n('meeting_t', 'default_projector_amendment_ids', 'projector_t', 'used_as_default_projector_for_amendment_in_meeting_id'); + + +-- definition trigger not null for meeting.default_projector_motion_block_ids against projector.used_as_default_projector_for_motion_block_in_meeting_id +CREATE CONSTRAINT TRIGGER tr_i_meeting_default_projector_motion_block_ids AFTER INSERT ON meeting_t INITIALLY DEFERRED +FOR EACH ROW EXECUTE FUNCTION check_not_null_for_1_n('meeting_t', 'default_projector_motion_block_ids', 'projector_t', 'used_as_default_projector_for_motion_block_in_meeting_id'); + +CREATE CONSTRAINT TRIGGER tr_ud_meeting_default_projector_motion_block_ids AFTER UPDATE OF used_as_default_projector_for_motion_block_in_meeting_id OR DELETE ON projector_t INITIALLY DEFERRED +FOR EACH ROW EXECUTE FUNCTION check_not_null_for_1_n('meeting_t', 'default_projector_motion_block_ids', 'projector_t', 'used_as_default_projector_for_motion_block_in_meeting_id'); + + +-- definition trigger not null for meeting.default_projector_assignment_ids against projector.used_as_default_projector_for_assignment_in_meeting_id +CREATE CONSTRAINT TRIGGER tr_i_meeting_default_projector_assignment_ids AFTER INSERT ON meeting_t INITIALLY DEFERRED +FOR EACH ROW EXECUTE FUNCTION check_not_null_for_1_n('meeting_t', 'default_projector_assignment_ids', 'projector_t', 'used_as_default_projector_for_assignment_in_meeting_id'); + +CREATE CONSTRAINT TRIGGER tr_ud_meeting_default_projector_assignment_ids AFTER UPDATE OF used_as_default_projector_for_assignment_in_meeting_id OR DELETE ON projector_t INITIALLY DEFERRED +FOR EACH ROW EXECUTE FUNCTION check_not_null_for_1_n('meeting_t', 'default_projector_assignment_ids', 'projector_t', 'used_as_default_projector_for_assignment_in_meeting_id'); + + +-- definition trigger not null for meeting.default_projector_mediafile_ids against projector.used_as_default_projector_for_mediafile_in_meeting_id +CREATE CONSTRAINT TRIGGER tr_i_meeting_default_projector_mediafile_ids AFTER INSERT ON meeting_t INITIALLY DEFERRED +FOR EACH ROW EXECUTE FUNCTION check_not_null_for_1_n('meeting_t', 'default_projector_mediafile_ids', 'projector_t', 'used_as_default_projector_for_mediafile_in_meeting_id'); + +CREATE CONSTRAINT TRIGGER tr_ud_meeting_default_projector_mediafile_ids AFTER UPDATE OF used_as_default_projector_for_mediafile_in_meeting_id OR DELETE ON projector_t INITIALLY DEFERRED +FOR EACH ROW EXECUTE FUNCTION check_not_null_for_1_n('meeting_t', 'default_projector_mediafile_ids', 'projector_t', 'used_as_default_projector_for_mediafile_in_meeting_id'); + + +-- definition trigger not null for meeting.default_projector_message_ids against projector.used_as_default_projector_for_message_in_meeting_id +CREATE CONSTRAINT TRIGGER tr_i_meeting_default_projector_message_ids AFTER INSERT ON meeting_t INITIALLY DEFERRED +FOR EACH ROW EXECUTE FUNCTION check_not_null_for_1_n('meeting_t', 'default_projector_message_ids', 'projector_t', 'used_as_default_projector_for_message_in_meeting_id'); + +CREATE CONSTRAINT TRIGGER tr_ud_meeting_default_projector_message_ids AFTER UPDATE OF used_as_default_projector_for_message_in_meeting_id OR DELETE ON projector_t INITIALLY DEFERRED +FOR EACH ROW EXECUTE FUNCTION check_not_null_for_1_n('meeting_t', 'default_projector_message_ids', 'projector_t', 'used_as_default_projector_for_message_in_meeting_id'); + + +-- definition trigger not null for meeting.default_projector_countdown_ids against projector.used_as_default_projector_for_countdown_in_meeting_id +CREATE CONSTRAINT TRIGGER tr_i_meeting_default_projector_countdown_ids AFTER INSERT ON meeting_t INITIALLY DEFERRED +FOR EACH ROW EXECUTE FUNCTION check_not_null_for_1_n('meeting_t', 'default_projector_countdown_ids', 'projector_t', 'used_as_default_projector_for_countdown_in_meeting_id'); + +CREATE CONSTRAINT TRIGGER tr_ud_meeting_default_projector_countdown_ids AFTER UPDATE OF used_as_default_projector_for_countdown_in_meeting_id OR DELETE ON projector_t INITIALLY DEFERRED +FOR EACH ROW EXECUTE FUNCTION check_not_null_for_1_n('meeting_t', 'default_projector_countdown_ids', 'projector_t', 'used_as_default_projector_for_countdown_in_meeting_id'); + + +-- definition trigger not null for meeting.default_projector_assignment_poll_ids against projector.used_as_default_projector_for_assignment_poll_in_meeting_id +CREATE CONSTRAINT TRIGGER tr_i_meeting_default_projector_assignment_poll_ids AFTER INSERT ON meeting_t INITIALLY DEFERRED +FOR EACH ROW EXECUTE FUNCTION check_not_null_for_1_n('meeting_t', 'default_projector_assignment_poll_ids', 'projector_t', 'used_as_default_projector_for_assignment_poll_in_meeting_id'); + +CREATE CONSTRAINT TRIGGER tr_ud_meeting_default_projector_assignment_poll_ids AFTER UPDATE OF used_as_default_projector_for_assignment_poll_in_meeting_id OR DELETE ON projector_t INITIALLY DEFERRED +FOR EACH ROW EXECUTE FUNCTION check_not_null_for_1_n('meeting_t', 'default_projector_assignment_poll_ids', 'projector_t', 'used_as_default_projector_for_assignment_poll_in_meeting_id'); + + +-- definition trigger not null for meeting.default_projector_motion_poll_ids against projector.used_as_default_projector_for_motion_poll_in_meeting_id +CREATE CONSTRAINT TRIGGER tr_i_meeting_default_projector_motion_poll_ids AFTER INSERT ON meeting_t INITIALLY DEFERRED +FOR EACH ROW EXECUTE FUNCTION check_not_null_for_1_n('meeting_t', 'default_projector_motion_poll_ids', 'projector_t', 'used_as_default_projector_for_motion_poll_in_meeting_id'); + +CREATE CONSTRAINT TRIGGER tr_ud_meeting_default_projector_motion_poll_ids AFTER UPDATE OF used_as_default_projector_for_motion_poll_in_meeting_id OR DELETE ON projector_t INITIALLY DEFERRED +FOR EACH ROW EXECUTE FUNCTION check_not_null_for_1_n('meeting_t', 'default_projector_motion_poll_ids', 'projector_t', 'used_as_default_projector_for_motion_poll_in_meeting_id'); + + +-- definition trigger not null for meeting.default_projector_poll_ids against projector.used_as_default_projector_for_poll_in_meeting_id +CREATE CONSTRAINT TRIGGER tr_i_meeting_default_projector_poll_ids AFTER INSERT ON meeting_t INITIALLY DEFERRED +FOR EACH ROW EXECUTE FUNCTION check_not_null_for_1_n('meeting_t', 'default_projector_poll_ids', 'projector_t', 'used_as_default_projector_for_poll_in_meeting_id'); + +CREATE CONSTRAINT TRIGGER tr_ud_meeting_default_projector_poll_ids AFTER UPDATE OF used_as_default_projector_for_poll_in_meeting_id OR DELETE ON projector_t INITIALLY DEFERRED +FOR EACH ROW EXECUTE FUNCTION check_not_null_for_1_n('meeting_t', 'default_projector_poll_ids', 'projector_t', 'used_as_default_projector_for_poll_in_meeting_id'); + + + + +-- Create triggers checking foreign_ids not null for n:m relationships + +-- definition trigger not null for meeting_user.group_ids against group.meeting_user_ids through nm_group_meeting_user_ids_meeting_user_t +CREATE CONSTRAINT TRIGGER tr_i_meeting_user_group_ids AFTER INSERT ON meeting_user_t INITIALLY DEFERRED +FOR EACH ROW EXECUTE FUNCTION check_not_null_for_n_m('nm_group_meeting_user_ids_meeting_user_t', 'meeting_user_t', 'group_ids', 'meeting_user_id'); + +CREATE CONSTRAINT TRIGGER tr_d_meeting_user_group_ids AFTER DELETE ON nm_group_meeting_user_ids_meeting_user_t INITIALLY DEFERRED +FOR EACH ROW EXECUTE FUNCTION check_not_null_for_n_m('nm_group_meeting_user_ids_meeting_user_t', 'meeting_user_t', 'group_ids', 'meeting_user_id', 'group_id', 'group', 'meeting_user_ids'); + + + + +-- Create triggers preventing mirrored duplicates in fields referencing themselves + +-- definition trigger unique ids pair for motion.identical_motion_ids +CREATE TRIGGER restrict_motion_identical_motion_ids BEFORE INSERT OR UPDATE ON nm_motion_identical_motion_ids_motion_t +FOR EACH ROW EXECUTE FUNCTION check_unique_ids_pair('identical_motion_id'); + + + + +-- Create triggers for notify +CREATE TRIGGER tr_log_action_worker AFTER INSERT OR UPDATE OR DELETE ON action_worker_t +FOR EACH ROW EXECUTE FUNCTION log_modified_models('action_worker'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON action_worker_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); + +CREATE TRIGGER tr_log_agenda_item AFTER INSERT OR UPDATE OR DELETE ON agenda_item_t +FOR EACH ROW EXECUTE FUNCTION log_modified_models('agenda_item'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON agenda_item_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); + + +CREATE TRIGGER tr_log_motion_content_object_id_motion_id AFTER INSERT OR UPDATE OF content_object_id_motion_id OR DELETE ON agenda_item_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('motion','content_object_id_motion_id'); + +CREATE TRIGGER tr_log_motion_block_content_object_id_motion_block_id AFTER INSERT OR UPDATE OF content_object_id_motion_block_id OR DELETE ON agenda_item_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('motion_block','content_object_id_motion_block_id'); + +CREATE TRIGGER tr_log_assignment_content_object_id_assignment_id AFTER INSERT OR UPDATE OF content_object_id_assignment_id OR DELETE ON agenda_item_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('assignment','content_object_id_assignment_id'); + +CREATE TRIGGER tr_log_topic_content_object_id_topic_id AFTER INSERT OR UPDATE OF content_object_id_topic_id OR DELETE ON agenda_item_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('topic','content_object_id_topic_id'); +CREATE TRIGGER tr_log_agenda_item_t_parent_id AFTER INSERT OR UPDATE OF parent_id OR DELETE ON agenda_item_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('agenda_item', 'parent_id'); +CREATE TRIGGER tr_log_agenda_item_t_meeting_id AFTER INSERT OR UPDATE OF meeting_id OR DELETE ON agenda_item_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting', 'meeting_id'); + +CREATE TRIGGER tr_log_assignment AFTER INSERT OR UPDATE OR DELETE ON assignment_t +FOR EACH ROW EXECUTE FUNCTION log_modified_models('assignment'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON assignment_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); + +CREATE TRIGGER tr_log_assignment_t_meeting_id AFTER INSERT OR UPDATE OF meeting_id OR DELETE ON assignment_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting', 'meeting_id'); + +CREATE TRIGGER tr_log_assignment_candidate AFTER INSERT OR UPDATE OR DELETE ON assignment_candidate_t +FOR EACH ROW EXECUTE FUNCTION log_modified_models('assignment_candidate'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON assignment_candidate_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); + +CREATE TRIGGER tr_log_assignment_candidate_t_assignment_id AFTER INSERT OR UPDATE OF assignment_id OR DELETE ON assignment_candidate_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('assignment', 'assignment_id'); +CREATE TRIGGER tr_log_assignment_candidate_t_meeting_user_id AFTER INSERT OR UPDATE OF meeting_user_id OR DELETE ON assignment_candidate_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting_user', 'meeting_user_id'); +CREATE TRIGGER tr_log_assignment_candidate_t_meeting_id AFTER INSERT OR UPDATE OF meeting_id OR DELETE ON assignment_candidate_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting', 'meeting_id'); + +CREATE TRIGGER tr_log_chat_group AFTER INSERT OR UPDATE OR DELETE ON chat_group_t +FOR EACH ROW EXECUTE FUNCTION log_modified_models('chat_group'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON chat_group_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); + + +CREATE TRIGGER tr_log_nm_chat_group_read_group_ids_group_t AFTER INSERT OR UPDATE OR DELETE ON nm_chat_group_read_group_ids_group_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('chat_group','chat_group_id','group','group_id'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON nm_chat_group_read_group_ids_group_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); + +CREATE TRIGGER tr_log_nm_chat_group_write_group_ids_group_t AFTER INSERT OR UPDATE OR DELETE ON nm_chat_group_write_group_ids_group_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('chat_group','chat_group_id','group','group_id'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON nm_chat_group_write_group_ids_group_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); +CREATE TRIGGER tr_log_chat_group_t_meeting_id AFTER INSERT OR UPDATE OF meeting_id OR DELETE ON chat_group_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting', 'meeting_id'); + +CREATE TRIGGER tr_log_chat_message AFTER INSERT OR UPDATE OR DELETE ON chat_message_t +FOR EACH ROW EXECUTE FUNCTION log_modified_models('chat_message'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON chat_message_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); + +CREATE TRIGGER tr_log_chat_message_t_meeting_user_id AFTER INSERT OR UPDATE OF meeting_user_id OR DELETE ON chat_message_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting_user', 'meeting_user_id'); +CREATE TRIGGER tr_log_chat_message_t_chat_group_id AFTER INSERT OR UPDATE OF chat_group_id OR DELETE ON chat_message_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('chat_group', 'chat_group_id'); +CREATE TRIGGER tr_log_chat_message_t_meeting_id AFTER INSERT OR UPDATE OF meeting_id OR DELETE ON chat_message_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting', 'meeting_id'); + +CREATE TRIGGER tr_log_committee AFTER INSERT OR UPDATE OR DELETE ON committee_t +FOR EACH ROW EXECUTE FUNCTION log_modified_models('committee'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON committee_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); + +CREATE TRIGGER tr_log_committee_t_default_meeting_id AFTER INSERT OR UPDATE OF default_meeting_id OR DELETE ON committee_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting', 'default_meeting_id'); + +CREATE TRIGGER tr_log_nm_committee_manager_ids_user_t AFTER INSERT OR UPDATE OR DELETE ON nm_committee_manager_ids_user_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('committee','committee_id','user','user_id'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON nm_committee_manager_ids_user_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); +CREATE TRIGGER tr_log_committee_t_parent_id AFTER INSERT OR UPDATE OF parent_id OR DELETE ON committee_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('committee', 'parent_id'); + +CREATE TRIGGER tr_log_nm_committee_all_child_ids_committee_t AFTER INSERT OR UPDATE OR DELETE ON nm_committee_all_child_ids_committee_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('committee','all_child_id','committee','all_parent_id'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON nm_committee_all_child_ids_committee_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); + +CREATE TRIGGER tr_log_nm_committee_forward_to_committee_ids_committee_t AFTER INSERT OR UPDATE OR DELETE ON nm_committee_forward_to_committee_ids_committee_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('committee','forward_to_committee_id','committee','receive_forwardings_from_committee_id'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON nm_committee_forward_to_committee_ids_committee_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); + +CREATE TRIGGER tr_log_gender AFTER INSERT OR UPDATE OR DELETE ON gender_t +FOR EACH ROW EXECUTE FUNCTION log_modified_models('gender'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON gender_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); + +CREATE TRIGGER tr_log_group AFTER INSERT OR UPDATE OR DELETE ON group_t +FOR EACH ROW EXECUTE FUNCTION log_modified_models('group'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON group_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); + + +CREATE TRIGGER tr_log_nm_group_meeting_user_ids_meeting_user_t AFTER INSERT OR UPDATE OR DELETE ON nm_group_meeting_user_ids_meeting_user_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('group','group_id','meeting_user','meeting_user_id'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON nm_group_meeting_user_ids_meeting_user_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); + +CREATE TRIGGER tr_log_nm_group_mmagi_meeting_mediafile_t AFTER INSERT OR UPDATE OR DELETE ON nm_group_mmagi_meeting_mediafile_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('group','group_id','meeting_mediafile','meeting_mediafile_id'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON nm_group_mmagi_meeting_mediafile_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); + +CREATE TRIGGER tr_log_nm_group_mmiagi_meeting_mediafile_t AFTER INSERT OR UPDATE OR DELETE ON nm_group_mmiagi_meeting_mediafile_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('group','group_id','meeting_mediafile','meeting_mediafile_id'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON nm_group_mmiagi_meeting_mediafile_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); + +CREATE TRIGGER tr_log_nm_group_read_comment_section_ids_motion_comment_section_t AFTER INSERT OR UPDATE OR DELETE ON nm_group_read_comment_section_ids_motion_comment_section_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('group','group_id','motion_comment_section','motion_comment_section_id'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON nm_group_read_comment_section_ids_motion_comment_section_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); + +CREATE TRIGGER tr_log_nm_group_write_comment_section_ids_motion_comment_section_t AFTER INSERT OR UPDATE OR DELETE ON nm_group_write_comment_section_ids_motion_comment_section_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('group','group_id','motion_comment_section','motion_comment_section_id'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON nm_group_write_comment_section_ids_motion_comment_section_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); + +CREATE TRIGGER tr_log_nm_group_poll_ids_poll_t AFTER INSERT OR UPDATE OR DELETE ON nm_group_poll_ids_poll_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('group','group_id','poll','poll_id'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON nm_group_poll_ids_poll_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); +CREATE TRIGGER tr_log_group_t_used_as_motion_poll_default_id AFTER INSERT OR UPDATE OF used_as_motion_poll_default_id OR DELETE ON group_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting', 'used_as_motion_poll_default_id'); +CREATE TRIGGER tr_log_group_t_used_as_assignment_poll_default_id AFTER INSERT OR UPDATE OF used_as_assignment_poll_default_id OR DELETE ON group_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting', 'used_as_assignment_poll_default_id'); +CREATE TRIGGER tr_log_group_t_used_as_topic_poll_default_id AFTER INSERT OR UPDATE OF used_as_topic_poll_default_id OR DELETE ON group_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting', 'used_as_topic_poll_default_id'); +CREATE TRIGGER tr_log_group_t_used_as_poll_default_id AFTER INSERT OR UPDATE OF used_as_poll_default_id OR DELETE ON group_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting', 'used_as_poll_default_id'); +CREATE TRIGGER tr_log_group_t_meeting_id AFTER INSERT OR UPDATE OF meeting_id OR DELETE ON group_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting', 'meeting_id'); + +CREATE TRIGGER tr_log_history_entry AFTER INSERT OR UPDATE OR DELETE ON history_entry_t +FOR EACH ROW EXECUTE FUNCTION log_modified_models('history_entry'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON history_entry_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); + + +CREATE TRIGGER tr_log_user_model_id_user_id AFTER INSERT OR UPDATE OF model_id_user_id OR DELETE ON history_entry_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('user','model_id_user_id'); + +CREATE TRIGGER tr_log_motion_model_id_motion_id AFTER INSERT OR UPDATE OF model_id_motion_id OR DELETE ON history_entry_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('motion','model_id_motion_id'); + +CREATE TRIGGER tr_log_assignment_model_id_assignment_id AFTER INSERT OR UPDATE OF model_id_assignment_id OR DELETE ON history_entry_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('assignment','model_id_assignment_id'); +CREATE TRIGGER tr_log_history_entry_t_position_id AFTER INSERT OR UPDATE OF position_id OR DELETE ON history_entry_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('history_position', 'position_id'); +CREATE TRIGGER tr_log_history_entry_t_meeting_id AFTER INSERT OR UPDATE OF meeting_id OR DELETE ON history_entry_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting', 'meeting_id'); + +CREATE TRIGGER tr_log_history_position AFTER INSERT OR UPDATE OR DELETE ON history_position_t +FOR EACH ROW EXECUTE FUNCTION log_modified_models('history_position'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON history_position_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); + +CREATE TRIGGER tr_log_history_position_t_user_id AFTER INSERT OR UPDATE OF user_id OR DELETE ON history_position_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('user', 'user_id'); + +CREATE TRIGGER tr_log_import_preview AFTER INSERT OR UPDATE OR DELETE ON import_preview_t +FOR EACH ROW EXECUTE FUNCTION log_modified_models('import_preview'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON import_preview_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); + +CREATE TRIGGER tr_log_list_of_speakers AFTER INSERT OR UPDATE OR DELETE ON list_of_speakers_t +FOR EACH ROW EXECUTE FUNCTION log_modified_models('list_of_speakers'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON list_of_speakers_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); + + +CREATE TRIGGER tr_log_motion_content_object_id_motion_id AFTER INSERT OR UPDATE OF content_object_id_motion_id OR DELETE ON list_of_speakers_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('motion','content_object_id_motion_id'); + +CREATE TRIGGER tr_log_motion_block_content_object_id_motion_block_id AFTER INSERT OR UPDATE OF content_object_id_motion_block_id OR DELETE ON list_of_speakers_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('motion_block','content_object_id_motion_block_id'); + +CREATE TRIGGER tr_log_assignment_content_object_id_assignment_id AFTER INSERT OR UPDATE OF content_object_id_assignment_id OR DELETE ON list_of_speakers_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('assignment','content_object_id_assignment_id'); + +CREATE TRIGGER tr_log_topic_content_object_id_topic_id AFTER INSERT OR UPDATE OF content_object_id_topic_id OR DELETE ON list_of_speakers_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('topic','content_object_id_topic_id'); + +CREATE TRIGGER tr_log_meeting_mediafile_content_object_id_meeting_mediafile_id AFTER INSERT OR UPDATE OF content_object_id_meeting_mediafile_id OR DELETE ON list_of_speakers_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting_mediafile','content_object_id_meeting_mediafile_id'); +CREATE TRIGGER tr_log_list_of_speakers_t_meeting_id AFTER INSERT OR UPDATE OF meeting_id OR DELETE ON list_of_speakers_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting', 'meeting_id'); + +CREATE TRIGGER tr_log_mediafile AFTER INSERT OR UPDATE OR DELETE ON mediafile_t +FOR EACH ROW EXECUTE FUNCTION log_modified_models('mediafile'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON mediafile_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); + +CREATE TRIGGER tr_log_mediafile_t_published_to_meetings_in_organization_id AFTER INSERT OR UPDATE OF published_to_meetings_in_organization_id OR DELETE ON mediafile_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('organization', 'published_to_meetings_in_organization_id'); +CREATE TRIGGER tr_log_mediafile_t_parent_id AFTER INSERT OR UPDATE OF parent_id OR DELETE ON mediafile_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('mediafile', 'parent_id'); + +CREATE TRIGGER tr_log_meeting_owner_id_meeting_id AFTER INSERT OR UPDATE OF owner_id_meeting_id OR DELETE ON mediafile_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting','owner_id_meeting_id'); + +CREATE TRIGGER tr_log_organization_owner_id_organization_id AFTER INSERT OR UPDATE OF owner_id_organization_id OR DELETE ON mediafile_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('organization','owner_id_organization_id'); + +CREATE TRIGGER tr_log_meeting AFTER INSERT OR UPDATE OR DELETE ON meeting_t +FOR EACH ROW EXECUTE FUNCTION log_modified_models('meeting'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON meeting_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); + +CREATE TRIGGER tr_log_meeting_t_is_active_in_organization_id AFTER INSERT OR UPDATE OF is_active_in_organization_id OR DELETE ON meeting_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('organization', 'is_active_in_organization_id'); +CREATE TRIGGER tr_log_meeting_t_is_archived_in_organization_id AFTER INSERT OR UPDATE OF is_archived_in_organization_id OR DELETE ON meeting_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('organization', 'is_archived_in_organization_id'); +CREATE TRIGGER tr_log_meeting_t_template_for_organization_id AFTER INSERT OR UPDATE OF template_for_organization_id OR DELETE ON meeting_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('organization', 'template_for_organization_id'); +CREATE TRIGGER tr_log_meeting_t_motions_default_workflow_id AFTER INSERT OR UPDATE OF motions_default_workflow_id OR DELETE ON meeting_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('motion_workflow', 'motions_default_workflow_id'); +CREATE TRIGGER tr_log_meeting_t_motions_default_amendment_workflow_id AFTER INSERT OR UPDATE OF motions_default_amendment_workflow_id OR DELETE ON meeting_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('motion_workflow', 'motions_default_amendment_workflow_id'); +CREATE TRIGGER tr_log_meeting_t_logo_projector_main_id AFTER INSERT OR UPDATE OF logo_projector_main_id OR DELETE ON meeting_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting_mediafile', 'logo_projector_main_id'); +CREATE TRIGGER tr_log_meeting_t_logo_projector_header_id AFTER INSERT OR UPDATE OF logo_projector_header_id OR DELETE ON meeting_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting_mediafile', 'logo_projector_header_id'); +CREATE TRIGGER tr_log_meeting_t_logo_web_header_id AFTER INSERT OR UPDATE OF logo_web_header_id OR DELETE ON meeting_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting_mediafile', 'logo_web_header_id'); +CREATE TRIGGER tr_log_meeting_t_logo_pdf_header_l_id AFTER INSERT OR UPDATE OF logo_pdf_header_l_id OR DELETE ON meeting_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting_mediafile', 'logo_pdf_header_l_id'); +CREATE TRIGGER tr_log_meeting_t_logo_pdf_header_r_id AFTER INSERT OR UPDATE OF logo_pdf_header_r_id OR DELETE ON meeting_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting_mediafile', 'logo_pdf_header_r_id'); +CREATE TRIGGER tr_log_meeting_t_logo_pdf_footer_l_id AFTER INSERT OR UPDATE OF logo_pdf_footer_l_id OR DELETE ON meeting_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting_mediafile', 'logo_pdf_footer_l_id'); +CREATE TRIGGER tr_log_meeting_t_logo_pdf_footer_r_id AFTER INSERT OR UPDATE OF logo_pdf_footer_r_id OR DELETE ON meeting_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting_mediafile', 'logo_pdf_footer_r_id'); +CREATE TRIGGER tr_log_meeting_t_logo_pdf_ballot_paper_id AFTER INSERT OR UPDATE OF logo_pdf_ballot_paper_id OR DELETE ON meeting_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting_mediafile', 'logo_pdf_ballot_paper_id'); +CREATE TRIGGER tr_log_meeting_t_font_regular_id AFTER INSERT OR UPDATE OF font_regular_id OR DELETE ON meeting_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting_mediafile', 'font_regular_id'); +CREATE TRIGGER tr_log_meeting_t_font_italic_id AFTER INSERT OR UPDATE OF font_italic_id OR DELETE ON meeting_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting_mediafile', 'font_italic_id'); +CREATE TRIGGER tr_log_meeting_t_font_bold_id AFTER INSERT OR UPDATE OF font_bold_id OR DELETE ON meeting_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting_mediafile', 'font_bold_id'); +CREATE TRIGGER tr_log_meeting_t_font_bold_italic_id AFTER INSERT OR UPDATE OF font_bold_italic_id OR DELETE ON meeting_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting_mediafile', 'font_bold_italic_id'); +CREATE TRIGGER tr_log_meeting_t_font_monospace_id AFTER INSERT OR UPDATE OF font_monospace_id OR DELETE ON meeting_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting_mediafile', 'font_monospace_id'); +CREATE TRIGGER tr_log_meeting_t_font_chyron_speaker_name_id AFTER INSERT OR UPDATE OF font_chyron_speaker_name_id OR DELETE ON meeting_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting_mediafile', 'font_chyron_speaker_name_id'); +CREATE TRIGGER tr_log_meeting_t_font_projector_h1_id AFTER INSERT OR UPDATE OF font_projector_h1_id OR DELETE ON meeting_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting_mediafile', 'font_projector_h1_id'); +CREATE TRIGGER tr_log_meeting_t_font_projector_h2_id AFTER INSERT OR UPDATE OF font_projector_h2_id OR DELETE ON meeting_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting_mediafile', 'font_projector_h2_id'); +CREATE TRIGGER tr_log_meeting_t_committee_id AFTER INSERT OR UPDATE OF committee_id OR DELETE ON meeting_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('committee', 'committee_id'); + +CREATE TRIGGER tr_log_nm_meeting_present_user_ids_user_t AFTER INSERT OR UPDATE OR DELETE ON nm_meeting_present_user_ids_user_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting','meeting_id','user','user_id'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON nm_meeting_present_user_ids_user_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); +CREATE TRIGGER tr_log_meeting_t_reference_projector_id AFTER INSERT OR UPDATE OF reference_projector_id OR DELETE ON meeting_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('projector', 'reference_projector_id'); +CREATE TRIGGER tr_log_meeting_t_list_of_speakers_countdown_id AFTER INSERT OR UPDATE OF list_of_speakers_countdown_id OR DELETE ON meeting_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('projector_countdown', 'list_of_speakers_countdown_id'); +CREATE TRIGGER tr_log_meeting_t_poll_countdown_id AFTER INSERT OR UPDATE OF poll_countdown_id OR DELETE ON meeting_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('projector_countdown', 'poll_countdown_id'); +CREATE TRIGGER tr_log_meeting_t_default_group_id AFTER INSERT OR UPDATE OF default_group_id OR DELETE ON meeting_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('group', 'default_group_id'); +CREATE TRIGGER tr_log_meeting_t_admin_group_id AFTER INSERT OR UPDATE OF admin_group_id OR DELETE ON meeting_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('group', 'admin_group_id'); +CREATE TRIGGER tr_log_meeting_t_anonymous_group_id AFTER INSERT OR UPDATE OF anonymous_group_id OR DELETE ON meeting_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('group', 'anonymous_group_id'); + +CREATE TRIGGER tr_log_meeting_mediafile AFTER INSERT OR UPDATE OR DELETE ON meeting_mediafile_t +FOR EACH ROW EXECUTE FUNCTION log_modified_models('meeting_mediafile'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON meeting_mediafile_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); + +CREATE TRIGGER tr_log_meeting_mediafile_t_mediafile_id AFTER INSERT OR UPDATE OF mediafile_id OR DELETE ON meeting_mediafile_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('mediafile', 'mediafile_id'); +CREATE TRIGGER tr_log_meeting_mediafile_t_meeting_id AFTER INSERT OR UPDATE OF meeting_id OR DELETE ON meeting_mediafile_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting', 'meeting_id'); + +CREATE TRIGGER tr_log_attachment_id_motion_id_gm_meeting_mediafile_attachment_ids_t AFTER INSERT OR UPDATE OF attachment_id_motion_id OR DELETE ON gm_meeting_mediafile_attachment_ids_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting_mediafile','meeting_mediafile_id','motion','attachment_id_motion_id'); + +CREATE TRIGGER tr_log_attachment_id_topic_id_gm_meeting_mediafile_attachment_ids_t AFTER INSERT OR UPDATE OF attachment_id_topic_id OR DELETE ON gm_meeting_mediafile_attachment_ids_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting_mediafile','meeting_mediafile_id','topic','attachment_id_topic_id'); + +CREATE TRIGGER tr_log_attachment_id_assignment_id_gm_meeting_mediafile_attachment_ids_t AFTER INSERT OR UPDATE OF attachment_id_assignment_id OR DELETE ON gm_meeting_mediafile_attachment_ids_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting_mediafile','meeting_mediafile_id','assignment','attachment_id_assignment_id'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON gm_meeting_mediafile_attachment_ids_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); + +CREATE TRIGGER tr_log_meeting_user AFTER INSERT OR UPDATE OR DELETE ON meeting_user_t +FOR EACH ROW EXECUTE FUNCTION log_modified_models('meeting_user'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON meeting_user_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); + +CREATE TRIGGER tr_log_meeting_user_t_user_id AFTER INSERT OR UPDATE OF user_id OR DELETE ON meeting_user_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('user', 'user_id'); +CREATE TRIGGER tr_log_meeting_user_t_meeting_id AFTER INSERT OR UPDATE OF meeting_id OR DELETE ON meeting_user_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting', 'meeting_id'); +CREATE TRIGGER tr_log_meeting_user_t_vote_delegated_to_id AFTER INSERT OR UPDATE OF vote_delegated_to_id OR DELETE ON meeting_user_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting_user', 'vote_delegated_to_id'); + +CREATE TRIGGER tr_log_nm_meeting_user_structure_level_ids_structure_level_t AFTER INSERT OR UPDATE OR DELETE ON nm_meeting_user_structure_level_ids_structure_level_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting_user','meeting_user_id','structure_level','structure_level_id'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON nm_meeting_user_structure_level_ids_structure_level_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); + +CREATE TRIGGER tr_log_motion AFTER INSERT OR UPDATE OR DELETE ON motion_t +FOR EACH ROW EXECUTE FUNCTION log_modified_models('motion'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON motion_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); + +CREATE TRIGGER tr_log_motion_t_lead_motion_id AFTER INSERT OR UPDATE OF lead_motion_id OR DELETE ON motion_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('motion', 'lead_motion_id'); +CREATE TRIGGER tr_log_motion_t_sort_parent_id AFTER INSERT OR UPDATE OF sort_parent_id OR DELETE ON motion_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('motion', 'sort_parent_id'); +CREATE TRIGGER tr_log_motion_t_origin_id AFTER INSERT OR UPDATE OF origin_id OR DELETE ON motion_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('motion', 'origin_id'); +CREATE TRIGGER tr_log_motion_t_origin_meeting_id AFTER INSERT OR UPDATE OF origin_meeting_id OR DELETE ON motion_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting', 'origin_meeting_id'); + +CREATE TRIGGER tr_log_nm_motion_all_derived_motion_ids_motion_t AFTER INSERT OR UPDATE OR DELETE ON nm_motion_all_derived_motion_ids_motion_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('motion','all_derived_motion_id','motion','all_origin_id'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON nm_motion_all_derived_motion_ids_motion_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); + +CREATE TRIGGER tr_log_nm_motion_identical_motion_ids_motion_t AFTER INSERT OR UPDATE OR DELETE ON nm_motion_identical_motion_ids_motion_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('motion','identical_motion_id_1','motion','identical_motion_id_2'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON nm_motion_identical_motion_ids_motion_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); +CREATE TRIGGER tr_log_motion_t_state_id AFTER INSERT OR UPDATE OF state_id OR DELETE ON motion_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('motion_state', 'state_id'); +CREATE TRIGGER tr_log_motion_t_recommendation_id AFTER INSERT OR UPDATE OF recommendation_id OR DELETE ON motion_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('motion_state', 'recommendation_id'); + +CREATE TRIGGER tr_log_state_extension_reference_id_motion_id_gm_motion_state_extension_reference_ids_t AFTER INSERT OR UPDATE OF state_extension_reference_id_motion_id OR DELETE ON gm_motion_state_extension_reference_ids_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('motion','motion_id','motion','state_extension_reference_id_motion_id'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON gm_motion_state_extension_reference_ids_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); + +CREATE TRIGGER tr_log_recommendation_extension_reference_id_motion_id_gm_motion_recommendation_extension_reference_ids_t AFTER INSERT OR UPDATE OF recommendation_extension_reference_id_motion_id OR DELETE ON gm_motion_recommendation_extension_reference_ids_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('motion','motion_id','motion','recommendation_extension_reference_id_motion_id'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON gm_motion_recommendation_extension_reference_ids_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); +CREATE TRIGGER tr_log_motion_t_category_id AFTER INSERT OR UPDATE OF category_id OR DELETE ON motion_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('motion_category', 'category_id'); +CREATE TRIGGER tr_log_motion_t_block_id AFTER INSERT OR UPDATE OF block_id OR DELETE ON motion_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('motion_block', 'block_id'); +CREATE TRIGGER tr_log_motion_t_meeting_id AFTER INSERT OR UPDATE OF meeting_id OR DELETE ON motion_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting', 'meeting_id'); + +CREATE TRIGGER tr_log_motion_block AFTER INSERT OR UPDATE OR DELETE ON motion_block_t +FOR EACH ROW EXECUTE FUNCTION log_modified_models('motion_block'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON motion_block_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); + +CREATE TRIGGER tr_log_motion_block_t_meeting_id AFTER INSERT OR UPDATE OF meeting_id OR DELETE ON motion_block_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting', 'meeting_id'); + +CREATE TRIGGER tr_log_motion_category AFTER INSERT OR UPDATE OR DELETE ON motion_category_t +FOR EACH ROW EXECUTE FUNCTION log_modified_models('motion_category'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON motion_category_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); + +CREATE TRIGGER tr_log_motion_category_t_parent_id AFTER INSERT OR UPDATE OF parent_id OR DELETE ON motion_category_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('motion_category', 'parent_id'); +CREATE TRIGGER tr_log_motion_category_t_meeting_id AFTER INSERT OR UPDATE OF meeting_id OR DELETE ON motion_category_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting', 'meeting_id'); + +CREATE TRIGGER tr_log_motion_change_recommendation AFTER INSERT OR UPDATE OR DELETE ON motion_change_recommendation_t +FOR EACH ROW EXECUTE FUNCTION log_modified_models('motion_change_recommendation'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON motion_change_recommendation_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); + +CREATE TRIGGER tr_log_motion_change_recommendation_t_motion_id AFTER INSERT OR UPDATE OF motion_id OR DELETE ON motion_change_recommendation_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('motion', 'motion_id'); +CREATE TRIGGER tr_log_motion_change_recommendation_t_meeting_id AFTER INSERT OR UPDATE OF meeting_id OR DELETE ON motion_change_recommendation_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting', 'meeting_id'); + +CREATE TRIGGER tr_log_motion_comment AFTER INSERT OR UPDATE OR DELETE ON motion_comment_t +FOR EACH ROW EXECUTE FUNCTION log_modified_models('motion_comment'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON motion_comment_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); + +CREATE TRIGGER tr_log_motion_comment_t_motion_id AFTER INSERT OR UPDATE OF motion_id OR DELETE ON motion_comment_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('motion', 'motion_id'); +CREATE TRIGGER tr_log_motion_comment_t_section_id AFTER INSERT OR UPDATE OF section_id OR DELETE ON motion_comment_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('motion_comment_section', 'section_id'); +CREATE TRIGGER tr_log_motion_comment_t_meeting_id AFTER INSERT OR UPDATE OF meeting_id OR DELETE ON motion_comment_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting', 'meeting_id'); + +CREATE TRIGGER tr_log_motion_comment_section AFTER INSERT OR UPDATE OR DELETE ON motion_comment_section_t +FOR EACH ROW EXECUTE FUNCTION log_modified_models('motion_comment_section'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON motion_comment_section_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); + +CREATE TRIGGER tr_log_motion_comment_section_t_meeting_id AFTER INSERT OR UPDATE OF meeting_id OR DELETE ON motion_comment_section_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting', 'meeting_id'); + +CREATE TRIGGER tr_log_motion_editor AFTER INSERT OR UPDATE OR DELETE ON motion_editor_t +FOR EACH ROW EXECUTE FUNCTION log_modified_models('motion_editor'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON motion_editor_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); + +CREATE TRIGGER tr_log_motion_editor_t_meeting_user_id AFTER INSERT OR UPDATE OF meeting_user_id OR DELETE ON motion_editor_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting_user', 'meeting_user_id'); +CREATE TRIGGER tr_log_motion_editor_t_motion_id AFTER INSERT OR UPDATE OF motion_id OR DELETE ON motion_editor_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('motion', 'motion_id'); +CREATE TRIGGER tr_log_motion_editor_t_meeting_id AFTER INSERT OR UPDATE OF meeting_id OR DELETE ON motion_editor_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting', 'meeting_id'); + +CREATE TRIGGER tr_log_motion_state AFTER INSERT OR UPDATE OR DELETE ON motion_state_t +FOR EACH ROW EXECUTE FUNCTION log_modified_models('motion_state'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON motion_state_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); + +CREATE TRIGGER tr_log_motion_state_t_submitter_withdraw_state_id AFTER INSERT OR UPDATE OF submitter_withdraw_state_id OR DELETE ON motion_state_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('motion_state', 'submitter_withdraw_state_id'); + +CREATE TRIGGER tr_log_nm_motion_state_next_state_ids_motion_state_t AFTER INSERT OR UPDATE OR DELETE ON nm_motion_state_next_state_ids_motion_state_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('motion_state','next_state_id','motion_state','previous_state_id'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON nm_motion_state_next_state_ids_motion_state_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); +CREATE TRIGGER tr_log_motion_state_t_workflow_id AFTER INSERT OR UPDATE OF workflow_id OR DELETE ON motion_state_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('motion_workflow', 'workflow_id'); +CREATE TRIGGER tr_log_motion_state_t_meeting_id AFTER INSERT OR UPDATE OF meeting_id OR DELETE ON motion_state_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting', 'meeting_id'); + +CREATE TRIGGER tr_log_motion_submitter AFTER INSERT OR UPDATE OR DELETE ON motion_submitter_t +FOR EACH ROW EXECUTE FUNCTION log_modified_models('motion_submitter'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON motion_submitter_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); + +CREATE TRIGGER tr_log_motion_submitter_t_meeting_user_id AFTER INSERT OR UPDATE OF meeting_user_id OR DELETE ON motion_submitter_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting_user', 'meeting_user_id'); +CREATE TRIGGER tr_log_motion_submitter_t_motion_id AFTER INSERT OR UPDATE OF motion_id OR DELETE ON motion_submitter_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('motion', 'motion_id'); +CREATE TRIGGER tr_log_motion_submitter_t_meeting_id AFTER INSERT OR UPDATE OF meeting_id OR DELETE ON motion_submitter_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting', 'meeting_id'); + +CREATE TRIGGER tr_log_motion_supporter AFTER INSERT OR UPDATE OR DELETE ON motion_supporter_t +FOR EACH ROW EXECUTE FUNCTION log_modified_models('motion_supporter'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON motion_supporter_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); + +CREATE TRIGGER tr_log_motion_supporter_t_meeting_user_id AFTER INSERT OR UPDATE OF meeting_user_id OR DELETE ON motion_supporter_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting_user', 'meeting_user_id'); +CREATE TRIGGER tr_log_motion_supporter_t_motion_id AFTER INSERT OR UPDATE OF motion_id OR DELETE ON motion_supporter_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('motion', 'motion_id'); +CREATE TRIGGER tr_log_motion_supporter_t_meeting_id AFTER INSERT OR UPDATE OF meeting_id OR DELETE ON motion_supporter_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting', 'meeting_id'); + +CREATE TRIGGER tr_log_motion_workflow AFTER INSERT OR UPDATE OR DELETE ON motion_workflow_t +FOR EACH ROW EXECUTE FUNCTION log_modified_models('motion_workflow'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON motion_workflow_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); + +CREATE TRIGGER tr_log_motion_workflow_t_first_state_id AFTER INSERT OR UPDATE OF first_state_id OR DELETE ON motion_workflow_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('motion_state', 'first_state_id'); +CREATE TRIGGER tr_log_motion_workflow_t_meeting_id AFTER INSERT OR UPDATE OF meeting_id OR DELETE ON motion_workflow_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting', 'meeting_id'); + +CREATE TRIGGER tr_log_motion_working_group_speaker AFTER INSERT OR UPDATE OR DELETE ON motion_working_group_speaker_t +FOR EACH ROW EXECUTE FUNCTION log_modified_models('motion_working_group_speaker'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON motion_working_group_speaker_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); + +CREATE TRIGGER tr_log_motion_working_group_speaker_t_meeting_user_id AFTER INSERT OR UPDATE OF meeting_user_id OR DELETE ON motion_working_group_speaker_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting_user', 'meeting_user_id'); +CREATE TRIGGER tr_log_motion_working_group_speaker_t_motion_id AFTER INSERT OR UPDATE OF motion_id OR DELETE ON motion_working_group_speaker_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('motion', 'motion_id'); +CREATE TRIGGER tr_log_motion_working_group_speaker_t_meeting_id AFTER INSERT OR UPDATE OF meeting_id OR DELETE ON motion_working_group_speaker_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting', 'meeting_id'); + +CREATE TRIGGER tr_log_option AFTER INSERT OR UPDATE OR DELETE ON option_t +FOR EACH ROW EXECUTE FUNCTION log_modified_models('option'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON option_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); + +CREATE TRIGGER tr_log_option_t_poll_id AFTER INSERT OR UPDATE OF poll_id OR DELETE ON option_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('poll', 'poll_id'); +CREATE TRIGGER tr_log_option_t_used_as_global_option_in_poll_id AFTER INSERT OR UPDATE OF used_as_global_option_in_poll_id OR DELETE ON option_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('poll', 'used_as_global_option_in_poll_id'); + +CREATE TRIGGER tr_log_motion_content_object_id_motion_id AFTER INSERT OR UPDATE OF content_object_id_motion_id OR DELETE ON option_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('motion','content_object_id_motion_id'); + +CREATE TRIGGER tr_log_user_content_object_id_user_id AFTER INSERT OR UPDATE OF content_object_id_user_id OR DELETE ON option_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('user','content_object_id_user_id'); + +CREATE TRIGGER tr_log_poll_candidate_list_content_object_id_poll_candidate_list_id AFTER INSERT OR UPDATE OF content_object_id_poll_candidate_list_id OR DELETE ON option_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('poll_candidate_list','content_object_id_poll_candidate_list_id'); +CREATE TRIGGER tr_log_option_t_meeting_id AFTER INSERT OR UPDATE OF meeting_id OR DELETE ON option_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting', 'meeting_id'); + +CREATE TRIGGER tr_log_organization AFTER INSERT OR UPDATE OR DELETE ON organization_t +FOR EACH ROW EXECUTE FUNCTION log_modified_models('organization'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON organization_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); + +CREATE TRIGGER tr_log_organization_t_theme_id AFTER INSERT OR UPDATE OF theme_id OR DELETE ON organization_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('theme', 'theme_id'); + +CREATE TRIGGER tr_log_organization_tag AFTER INSERT OR UPDATE OR DELETE ON organization_tag_t +FOR EACH ROW EXECUTE FUNCTION log_modified_models('organization_tag'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON organization_tag_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); + + +CREATE TRIGGER tr_log_tagged_id_committee_id_gm_organization_tag_tagged_ids_t AFTER INSERT OR UPDATE OF tagged_id_committee_id OR DELETE ON gm_organization_tag_tagged_ids_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('organization_tag','organization_tag_id','committee','tagged_id_committee_id'); + +CREATE TRIGGER tr_log_tagged_id_meeting_id_gm_organization_tag_tagged_ids_t AFTER INSERT OR UPDATE OF tagged_id_meeting_id OR DELETE ON gm_organization_tag_tagged_ids_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('organization_tag','organization_tag_id','meeting','tagged_id_meeting_id'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON gm_organization_tag_tagged_ids_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); + +CREATE TRIGGER tr_log_personal_note AFTER INSERT OR UPDATE OR DELETE ON personal_note_t +FOR EACH ROW EXECUTE FUNCTION log_modified_models('personal_note'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON personal_note_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); + +CREATE TRIGGER tr_log_personal_note_t_meeting_user_id AFTER INSERT OR UPDATE OF meeting_user_id OR DELETE ON personal_note_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting_user', 'meeting_user_id'); + +CREATE TRIGGER tr_log_motion_content_object_id_motion_id AFTER INSERT OR UPDATE OF content_object_id_motion_id OR DELETE ON personal_note_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('motion','content_object_id_motion_id'); +CREATE TRIGGER tr_log_personal_note_t_meeting_id AFTER INSERT OR UPDATE OF meeting_id OR DELETE ON personal_note_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting', 'meeting_id'); + +CREATE TRIGGER tr_log_point_of_order_category AFTER INSERT OR UPDATE OR DELETE ON point_of_order_category_t +FOR EACH ROW EXECUTE FUNCTION log_modified_models('point_of_order_category'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON point_of_order_category_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); + +CREATE TRIGGER tr_log_point_of_order_category_t_meeting_id AFTER INSERT OR UPDATE OF meeting_id OR DELETE ON point_of_order_category_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting', 'meeting_id'); + +CREATE TRIGGER tr_log_poll AFTER INSERT OR UPDATE OR DELETE ON poll_t +FOR EACH ROW EXECUTE FUNCTION log_modified_models('poll'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON poll_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); + + +CREATE TRIGGER tr_log_motion_content_object_id_motion_id AFTER INSERT OR UPDATE OF content_object_id_motion_id OR DELETE ON poll_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('motion','content_object_id_motion_id'); + +CREATE TRIGGER tr_log_assignment_content_object_id_assignment_id AFTER INSERT OR UPDATE OF content_object_id_assignment_id OR DELETE ON poll_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('assignment','content_object_id_assignment_id'); + +CREATE TRIGGER tr_log_topic_content_object_id_topic_id AFTER INSERT OR UPDATE OF content_object_id_topic_id OR DELETE ON poll_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('topic','content_object_id_topic_id'); +CREATE TRIGGER tr_log_poll_t_global_option_id AFTER INSERT OR UPDATE OF global_option_id OR DELETE ON poll_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('option', 'global_option_id'); + +CREATE TRIGGER tr_log_nm_poll_voted_ids_user_t AFTER INSERT OR UPDATE OR DELETE ON nm_poll_voted_ids_user_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('poll','poll_id','user','user_id'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON nm_poll_voted_ids_user_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); +CREATE TRIGGER tr_log_poll_t_meeting_id AFTER INSERT OR UPDATE OF meeting_id OR DELETE ON poll_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting', 'meeting_id'); + +CREATE TRIGGER tr_log_poll_candidate AFTER INSERT OR UPDATE OR DELETE ON poll_candidate_t +FOR EACH ROW EXECUTE FUNCTION log_modified_models('poll_candidate'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON poll_candidate_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); + +CREATE TRIGGER tr_log_poll_candidate_t_poll_candidate_list_id AFTER INSERT OR UPDATE OF poll_candidate_list_id OR DELETE ON poll_candidate_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('poll_candidate_list', 'poll_candidate_list_id'); +CREATE TRIGGER tr_log_poll_candidate_t_user_id AFTER INSERT OR UPDATE OF user_id OR DELETE ON poll_candidate_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('user', 'user_id'); +CREATE TRIGGER tr_log_poll_candidate_t_meeting_id AFTER INSERT OR UPDATE OF meeting_id OR DELETE ON poll_candidate_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting', 'meeting_id'); + +CREATE TRIGGER tr_log_poll_candidate_list AFTER INSERT OR UPDATE OR DELETE ON poll_candidate_list_t +FOR EACH ROW EXECUTE FUNCTION log_modified_models('poll_candidate_list'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON poll_candidate_list_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); + +CREATE TRIGGER tr_log_poll_candidate_list_t_meeting_id AFTER INSERT OR UPDATE OF meeting_id OR DELETE ON poll_candidate_list_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting', 'meeting_id'); + +CREATE TRIGGER tr_log_projection AFTER INSERT OR UPDATE OR DELETE ON projection_t +FOR EACH ROW EXECUTE FUNCTION log_modified_models('projection'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON projection_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); + +CREATE TRIGGER tr_log_projection_t_current_projector_id AFTER INSERT OR UPDATE OF current_projector_id OR DELETE ON projection_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('projector', 'current_projector_id'); +CREATE TRIGGER tr_log_projection_t_preview_projector_id AFTER INSERT OR UPDATE OF preview_projector_id OR DELETE ON projection_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('projector', 'preview_projector_id'); +CREATE TRIGGER tr_log_projection_t_history_projector_id AFTER INSERT OR UPDATE OF history_projector_id OR DELETE ON projection_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('projector', 'history_projector_id'); + +CREATE TRIGGER tr_log_meeting_content_object_id_meeting_id AFTER INSERT OR UPDATE OF content_object_id_meeting_id OR DELETE ON projection_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting','content_object_id_meeting_id'); + +CREATE TRIGGER tr_log_motion_content_object_id_motion_id AFTER INSERT OR UPDATE OF content_object_id_motion_id OR DELETE ON projection_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('motion','content_object_id_motion_id'); + +CREATE TRIGGER tr_log_meeting_mediafile_content_object_id_meeting_mediafile_id AFTER INSERT OR UPDATE OF content_object_id_meeting_mediafile_id OR DELETE ON projection_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting_mediafile','content_object_id_meeting_mediafile_id'); + +CREATE TRIGGER tr_log_list_of_speakers_content_object_id_list_of_speakers_id AFTER INSERT OR UPDATE OF content_object_id_list_of_speakers_id OR DELETE ON projection_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('list_of_speakers','content_object_id_list_of_speakers_id'); + +CREATE TRIGGER tr_log_motion_block_content_object_id_motion_block_id AFTER INSERT OR UPDATE OF content_object_id_motion_block_id OR DELETE ON projection_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('motion_block','content_object_id_motion_block_id'); + +CREATE TRIGGER tr_log_assignment_content_object_id_assignment_id AFTER INSERT OR UPDATE OF content_object_id_assignment_id OR DELETE ON projection_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('assignment','content_object_id_assignment_id'); + +CREATE TRIGGER tr_log_agenda_item_content_object_id_agenda_item_id AFTER INSERT OR UPDATE OF content_object_id_agenda_item_id OR DELETE ON projection_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('agenda_item','content_object_id_agenda_item_id'); + +CREATE TRIGGER tr_log_topic_content_object_id_topic_id AFTER INSERT OR UPDATE OF content_object_id_topic_id OR DELETE ON projection_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('topic','content_object_id_topic_id'); + +CREATE TRIGGER tr_log_poll_content_object_id_poll_id AFTER INSERT OR UPDATE OF content_object_id_poll_id OR DELETE ON projection_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('poll','content_object_id_poll_id'); + +CREATE TRIGGER tr_log_projector_message_content_object_id_projector_message_id AFTER INSERT OR UPDATE OF content_object_id_projector_message_id OR DELETE ON projection_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('projector_message','content_object_id_projector_message_id'); + +CREATE TRIGGER tr_log_projector_countdown_content_object_id_projector_countdown_id AFTER INSERT OR UPDATE OF content_object_id_projector_countdown_id OR DELETE ON projection_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('projector_countdown','content_object_id_projector_countdown_id'); +CREATE TRIGGER tr_log_projection_t_meeting_id AFTER INSERT OR UPDATE OF meeting_id OR DELETE ON projection_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting', 'meeting_id'); + +CREATE TRIGGER tr_log_projector AFTER INSERT OR UPDATE OR DELETE ON projector_t +FOR EACH ROW EXECUTE FUNCTION log_modified_models('projector'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON projector_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); + +CREATE TRIGGER tr_log_projector_t_used_as_default_projector_for_agenda_item_li AFTER INSERT OR UPDATE OF used_as_default_projector_for_agenda_item_list_in_meeting_id OR DELETE ON projector_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting', 'used_as_default_projector_for_agenda_item_list_in_meeting_id'); +CREATE TRIGGER tr_log_projector_t_used_as_default_projector_for_topic_in_meeti AFTER INSERT OR UPDATE OF used_as_default_projector_for_topic_in_meeting_id OR DELETE ON projector_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting', 'used_as_default_projector_for_topic_in_meeting_id'); +CREATE TRIGGER tr_log_projector_t_used_as_default_projector_for_list_of_speake AFTER INSERT OR UPDATE OF used_as_default_projector_for_list_of_speakers_in_meeting_id OR DELETE ON projector_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting', 'used_as_default_projector_for_list_of_speakers_in_meeting_id'); +CREATE TRIGGER tr_log_projector_t_used_as_default_projector_for_current_los_in AFTER INSERT OR UPDATE OF used_as_default_projector_for_current_los_in_meeting_id OR DELETE ON projector_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting', 'used_as_default_projector_for_current_los_in_meeting_id'); +CREATE TRIGGER tr_log_projector_t_used_as_default_projector_for_motion_in_meet AFTER INSERT OR UPDATE OF used_as_default_projector_for_motion_in_meeting_id OR DELETE ON projector_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting', 'used_as_default_projector_for_motion_in_meeting_id'); +CREATE TRIGGER tr_log_projector_t_used_as_default_projector_for_amendment_in_m AFTER INSERT OR UPDATE OF used_as_default_projector_for_amendment_in_meeting_id OR DELETE ON projector_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting', 'used_as_default_projector_for_amendment_in_meeting_id'); +CREATE TRIGGER tr_log_projector_t_used_as_default_projector_for_motion_block_i AFTER INSERT OR UPDATE OF used_as_default_projector_for_motion_block_in_meeting_id OR DELETE ON projector_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting', 'used_as_default_projector_for_motion_block_in_meeting_id'); +CREATE TRIGGER tr_log_projector_t_used_as_default_projector_for_assignment_in_ AFTER INSERT OR UPDATE OF used_as_default_projector_for_assignment_in_meeting_id OR DELETE ON projector_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting', 'used_as_default_projector_for_assignment_in_meeting_id'); +CREATE TRIGGER tr_log_projector_t_used_as_default_projector_for_mediafile_in_m AFTER INSERT OR UPDATE OF used_as_default_projector_for_mediafile_in_meeting_id OR DELETE ON projector_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting', 'used_as_default_projector_for_mediafile_in_meeting_id'); +CREATE TRIGGER tr_log_projector_t_used_as_default_projector_for_message_in_mee AFTER INSERT OR UPDATE OF used_as_default_projector_for_message_in_meeting_id OR DELETE ON projector_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting', 'used_as_default_projector_for_message_in_meeting_id'); +CREATE TRIGGER tr_log_projector_t_used_as_default_projector_for_countdown_in_m AFTER INSERT OR UPDATE OF used_as_default_projector_for_countdown_in_meeting_id OR DELETE ON projector_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting', 'used_as_default_projector_for_countdown_in_meeting_id'); +CREATE TRIGGER tr_log_projector_t_used_as_default_projector_for_assignment_pol AFTER INSERT OR UPDATE OF used_as_default_projector_for_assignment_poll_in_meeting_id OR DELETE ON projector_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting', 'used_as_default_projector_for_assignment_poll_in_meeting_id'); +CREATE TRIGGER tr_log_projector_t_used_as_default_projector_for_motion_poll_in AFTER INSERT OR UPDATE OF used_as_default_projector_for_motion_poll_in_meeting_id OR DELETE ON projector_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting', 'used_as_default_projector_for_motion_poll_in_meeting_id'); +CREATE TRIGGER tr_log_projector_t_used_as_default_projector_for_poll_in_meetin AFTER INSERT OR UPDATE OF used_as_default_projector_for_poll_in_meeting_id OR DELETE ON projector_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting', 'used_as_default_projector_for_poll_in_meeting_id'); +CREATE TRIGGER tr_log_projector_t_meeting_id AFTER INSERT OR UPDATE OF meeting_id OR DELETE ON projector_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting', 'meeting_id'); + +CREATE TRIGGER tr_log_projector_countdown AFTER INSERT OR UPDATE OR DELETE ON projector_countdown_t +FOR EACH ROW EXECUTE FUNCTION log_modified_models('projector_countdown'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON projector_countdown_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); + +CREATE TRIGGER tr_log_projector_countdown_t_meeting_id AFTER INSERT OR UPDATE OF meeting_id OR DELETE ON projector_countdown_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting', 'meeting_id'); + +CREATE TRIGGER tr_log_projector_message AFTER INSERT OR UPDATE OR DELETE ON projector_message_t +FOR EACH ROW EXECUTE FUNCTION log_modified_models('projector_message'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON projector_message_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); + +CREATE TRIGGER tr_log_projector_message_t_meeting_id AFTER INSERT OR UPDATE OF meeting_id OR DELETE ON projector_message_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting', 'meeting_id'); + +CREATE TRIGGER tr_log_speaker AFTER INSERT OR UPDATE OR DELETE ON speaker_t +FOR EACH ROW EXECUTE FUNCTION log_modified_models('speaker'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON speaker_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); + +CREATE TRIGGER tr_log_speaker_t_list_of_speakers_id AFTER INSERT OR UPDATE OF list_of_speakers_id OR DELETE ON speaker_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('list_of_speakers', 'list_of_speakers_id'); +CREATE TRIGGER tr_log_speaker_t_structure_level_list_of_speakers_id AFTER INSERT OR UPDATE OF structure_level_list_of_speakers_id OR DELETE ON speaker_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('structure_level_list_of_speakers', 'structure_level_list_of_speakers_id'); +CREATE TRIGGER tr_log_speaker_t_meeting_user_id AFTER INSERT OR UPDATE OF meeting_user_id OR DELETE ON speaker_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting_user', 'meeting_user_id'); +CREATE TRIGGER tr_log_speaker_t_point_of_order_category_id AFTER INSERT OR UPDATE OF point_of_order_category_id OR DELETE ON speaker_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('point_of_order_category', 'point_of_order_category_id'); +CREATE TRIGGER tr_log_speaker_t_meeting_id AFTER INSERT OR UPDATE OF meeting_id OR DELETE ON speaker_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting', 'meeting_id'); + +CREATE TRIGGER tr_log_structure_level AFTER INSERT OR UPDATE OR DELETE ON structure_level_t +FOR EACH ROW EXECUTE FUNCTION log_modified_models('structure_level'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON structure_level_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); + +CREATE TRIGGER tr_log_structure_level_t_meeting_id AFTER INSERT OR UPDATE OF meeting_id OR DELETE ON structure_level_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting', 'meeting_id'); + +CREATE TRIGGER tr_log_structure_level_list_of_speakers AFTER INSERT OR UPDATE OR DELETE ON structure_level_list_of_speakers_t +FOR EACH ROW EXECUTE FUNCTION log_modified_models('structure_level_list_of_speakers'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON structure_level_list_of_speakers_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); + +CREATE TRIGGER tr_log_structure_level_list_of_speakers_t_structure_level_id AFTER INSERT OR UPDATE OF structure_level_id OR DELETE ON structure_level_list_of_speakers_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('structure_level', 'structure_level_id'); +CREATE TRIGGER tr_log_structure_level_list_of_speakers_t_list_of_speakers_id AFTER INSERT OR UPDATE OF list_of_speakers_id OR DELETE ON structure_level_list_of_speakers_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('list_of_speakers', 'list_of_speakers_id'); +CREATE TRIGGER tr_log_structure_level_list_of_speakers_t_meeting_id AFTER INSERT OR UPDATE OF meeting_id OR DELETE ON structure_level_list_of_speakers_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting', 'meeting_id'); + +CREATE TRIGGER tr_log_tag AFTER INSERT OR UPDATE OR DELETE ON tag_t +FOR EACH ROW EXECUTE FUNCTION log_modified_models('tag'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON tag_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); + + +CREATE TRIGGER tr_log_tagged_id_agenda_item_id_gm_tag_tagged_ids_t AFTER INSERT OR UPDATE OF tagged_id_agenda_item_id OR DELETE ON gm_tag_tagged_ids_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('tag','tag_id','agenda_item','tagged_id_agenda_item_id'); + +CREATE TRIGGER tr_log_tagged_id_assignment_id_gm_tag_tagged_ids_t AFTER INSERT OR UPDATE OF tagged_id_assignment_id OR DELETE ON gm_tag_tagged_ids_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('tag','tag_id','assignment','tagged_id_assignment_id'); + +CREATE TRIGGER tr_log_tagged_id_motion_id_gm_tag_tagged_ids_t AFTER INSERT OR UPDATE OF tagged_id_motion_id OR DELETE ON gm_tag_tagged_ids_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('tag','tag_id','motion','tagged_id_motion_id'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON gm_tag_tagged_ids_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); +CREATE TRIGGER tr_log_tag_t_meeting_id AFTER INSERT OR UPDATE OF meeting_id OR DELETE ON tag_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting', 'meeting_id'); + +CREATE TRIGGER tr_log_theme AFTER INSERT OR UPDATE OR DELETE ON theme_t +FOR EACH ROW EXECUTE FUNCTION log_modified_models('theme'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON theme_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); + +CREATE TRIGGER tr_log_topic AFTER INSERT OR UPDATE OR DELETE ON topic_t +FOR EACH ROW EXECUTE FUNCTION log_modified_models('topic'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON topic_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); + +CREATE TRIGGER tr_log_topic_t_meeting_id AFTER INSERT OR UPDATE OF meeting_id OR DELETE ON topic_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting', 'meeting_id'); + +CREATE TRIGGER tr_log_user AFTER INSERT OR UPDATE OR DELETE ON user_t +FOR EACH ROW EXECUTE FUNCTION log_modified_models('user'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON user_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); + +CREATE TRIGGER tr_log_user_t_gender_id AFTER INSERT OR UPDATE OF gender_id OR DELETE ON user_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('gender', 'gender_id'); +CREATE TRIGGER tr_log_user_t_home_committee_id AFTER INSERT OR UPDATE OF home_committee_id OR DELETE ON user_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('committee', 'home_committee_id'); + +CREATE TRIGGER tr_log_vote AFTER INSERT OR UPDATE OR DELETE ON vote_t +FOR EACH ROW EXECUTE FUNCTION log_modified_models('vote'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON vote_t +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); + +CREATE TRIGGER tr_log_vote_t_option_id AFTER INSERT OR UPDATE OF option_id OR DELETE ON vote_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('option', 'option_id'); +CREATE TRIGGER tr_log_vote_t_user_id AFTER INSERT OR UPDATE OF user_id OR DELETE ON vote_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('user', 'user_id'); +CREATE TRIGGER tr_log_vote_t_delegated_user_id AFTER INSERT OR UPDATE OF delegated_user_id OR DELETE ON vote_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('user', 'delegated_user_id'); +CREATE TRIGGER tr_log_vote_t_meeting_id AFTER INSERT OR UPDATE OF meeting_id OR DELETE ON vote_t +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('meeting', 'meeting_id'); + + +/* Relation-list infos +Generated: What will be generated for left field + FIELD: a usual Database field + SQL: a sql-expression in a view + ***: Error +Field Attributes:Field Attributes opposite side + 1: cardinality 1 + 1G: cardinality 1 with generic-relation field + n: cardinality n + nG: cardinality n with generic-relation-list field + t: "to" defined + r: "reference" defined + s: sql directive inclusive sql-statement + R: Required +Model.Field -> Model.Field + model.field names +*/ + +/* +FIELD 1GrR:1t,1t,1t,1tR => agenda_item/content_object_id:-> motion/agenda_item_id,motion_block/agenda_item_id,assignment/agenda_item_id,topic/agenda_item_id +FIELD 1r:nt => agenda_item/parent_id:-> agenda_item/child_ids +SQL nt:1r => agenda_item/child_ids:-> agenda_item/parent_id +SQL nt:nGt => agenda_item/tag_ids:-> tag/tagged_ids +SQL nt:1GrR => agenda_item/projection_ids:-> projection/content_object_id +FIELD 1rR:nt => agenda_item/meeting_id:-> meeting/agenda_item_ids + +SQL nt:1rR => assignment/candidate_ids:-> assignment_candidate/assignment_id +SQL nt:1GrR => assignment/poll_ids:-> poll/content_object_id +SQL 1t:1GrR => assignment/agenda_item_id:-> agenda_item/content_object_id +SQL 1tR:1GrR => assignment/list_of_speakers_id:-> list_of_speakers/content_object_id +SQL nt:nGt => assignment/tag_ids:-> tag/tagged_ids +SQL nt:nGt => assignment/attachment_meeting_mediafile_ids:-> meeting_mediafile/attachment_ids +SQL nt:1GrR => assignment/projection_ids:-> projection/content_object_id +FIELD 1rR:nt => assignment/meeting_id:-> meeting/assignment_ids +SQL nt:1Gr => assignment/history_entry_ids:-> history_entry/model_id + +FIELD 1rR:nt => assignment_candidate/assignment_id:-> assignment/candidate_ids +FIELD 1r:nt => assignment_candidate/meeting_user_id:-> meeting_user/assignment_candidate_ids +FIELD 1rR:nt => assignment_candidate/meeting_id:-> meeting/assignment_candidate_ids + +SQL nt:1rR => chat_group/chat_message_ids:-> chat_message/chat_group_id +SQL nt:nt => chat_group/read_group_ids:-> group/read_chat_group_ids +SQL nt:nt => chat_group/write_group_ids:-> group/write_chat_group_ids +FIELD 1rR:nt => chat_group/meeting_id:-> meeting/chat_group_ids + +FIELD 1r:nt => chat_message/meeting_user_id:-> meeting_user/chat_message_ids +FIELD 1rR:nt => chat_message/chat_group_id:-> chat_group/chat_message_ids +FIELD 1rR:nt => chat_message/meeting_id:-> meeting/chat_message_ids + +SQL nt:1rR => committee/meeting_ids:-> meeting/committee_id +FIELD 1r:1t => committee/default_meeting_id:-> meeting/default_meeting_for_committee_id +SQL nts:nts => committee/user_ids:-> user/committee_ids +SQL nt:nt => committee/manager_ids:-> user/committee_management_ids +FIELD 1r:nt => committee/parent_id:-> committee/child_ids +SQL nt:1r => committee/child_ids:-> committee/parent_id +SQL nt:nt => committee/all_parent_ids:-> committee/all_child_ids +SQL nt:nt => committee/all_child_ids:-> committee/all_parent_ids +SQL nt:1r => committee/native_user_ids:-> user/home_committee_id +SQL nt:nt => committee/forward_to_committee_ids:-> committee/receive_forwardings_from_committee_ids +SQL nt:nt => committee/receive_forwardings_from_committee_ids:-> committee/forward_to_committee_ids +SQL nt:nGt => committee/organization_tag_ids:-> organization_tag/tagged_ids + +SQL nr:1r => gender/user_ids:-> user/gender_id + +SQL nt:ntR => group/meeting_user_ids:-> meeting_user/group_ids +SQL 1t:1rR => group/default_group_for_meeting_id:-> meeting/default_group_id +SQL 1t:1r => group/admin_group_for_meeting_id:-> meeting/admin_group_id +SQL 1t:1r => group/anonymous_group_for_meeting_id:-> meeting/anonymous_group_id +SQL nt:nt => group/meeting_mediafile_access_group_ids:-> meeting_mediafile/access_group_ids +SQL nt:nt => group/meeting_mediafile_inherited_access_group_ids:-> meeting_mediafile/inherited_access_group_ids +SQL nt:nt => group/read_comment_section_ids:-> motion_comment_section/read_group_ids +SQL nt:nt => group/write_comment_section_ids:-> motion_comment_section/write_group_ids +SQL nt:nt => group/read_chat_group_ids:-> chat_group/read_group_ids +SQL nt:nt => group/write_chat_group_ids:-> chat_group/write_group_ids +SQL nt:nt => group/poll_ids:-> poll/entitled_group_ids +FIELD 1r:nt => group/used_as_motion_poll_default_id:-> meeting/motion_poll_default_group_ids +FIELD 1r:nt => group/used_as_assignment_poll_default_id:-> meeting/assignment_poll_default_group_ids +FIELD 1r:nt => group/used_as_topic_poll_default_id:-> meeting/topic_poll_default_group_ids +FIELD 1r:nt => group/used_as_poll_default_id:-> meeting/poll_default_group_ids +FIELD 1rR:nt => group/meeting_id:-> meeting/group_ids + +FIELD 1Gr:nt,nt,nt => history_entry/model_id:-> user/history_entry_ids,motion/history_entry_ids,assignment/history_entry_ids +FIELD 1rR:nt => history_entry/position_id:-> history_position/entry_ids +FIELD 1r:nt => history_entry/meeting_id:-> meeting/relevant_history_entry_ids + +FIELD 1r:nt => history_position/user_id:-> user/history_position_ids +SQL nt:1rR => history_position/entry_ids:-> history_entry/position_id + +FIELD 1GrR:1tR,1tR,1tR,1tR,1t => list_of_speakers/content_object_id:-> motion/list_of_speakers_id,motion_block/list_of_speakers_id,assignment/list_of_speakers_id,topic/list_of_speakers_id,meeting_mediafile/list_of_speakers_id +SQL nt:1rR => list_of_speakers/speaker_ids:-> speaker/list_of_speakers_id +SQL nt:1rR => list_of_speakers/structure_level_list_of_speakers_ids:-> structure_level_list_of_speakers/list_of_speakers_id +SQL nt:1GrR => list_of_speakers/projection_ids:-> projection/content_object_id +FIELD 1rR:nt => list_of_speakers/meeting_id:-> meeting/list_of_speakers_ids + +FIELD 1r:nt => mediafile/published_to_meetings_in_organization_id:-> organization/published_mediafile_ids +FIELD 1r:nt => mediafile/parent_id:-> mediafile/child_ids +SQL nt:1r => mediafile/child_ids:-> mediafile/parent_id +FIELD 1GrR:nt,nt => mediafile/owner_id:-> meeting/mediafile_ids,organization/mediafile_ids +SQL nt:1rR => mediafile/meeting_mediafile_ids:-> meeting_mediafile/mediafile_id + +FIELD 1r:nt => meeting/is_active_in_organization_id:-> organization/active_meeting_ids +FIELD 1r:nt => meeting/is_archived_in_organization_id:-> organization/archived_meeting_ids +FIELD 1r:nt => meeting/template_for_organization_id:-> organization/template_meeting_ids +FIELD 1rR:1t => meeting/motions_default_workflow_id:-> motion_workflow/default_workflow_meeting_id +FIELD 1rR:1t => meeting/motions_default_amendment_workflow_id:-> motion_workflow/default_amendment_workflow_meeting_id +SQL nt:1r => meeting/motion_poll_default_group_ids:-> group/used_as_motion_poll_default_id +SQL nr:1rR => meeting/poll_candidate_list_ids:-> poll_candidate_list/meeting_id +SQL nr:1rR => meeting/poll_candidate_ids:-> poll_candidate/meeting_id +SQL nt:1rR => meeting/meeting_user_ids:-> meeting_user/meeting_id +SQL nt:1r => meeting/assignment_poll_default_group_ids:-> group/used_as_assignment_poll_default_id +SQL nt:1r => meeting/poll_default_group_ids:-> group/used_as_poll_default_id +SQL nt:1r => meeting/topic_poll_default_group_ids:-> group/used_as_topic_poll_default_id +SQL nt:1rR => meeting/projector_ids:-> projector/meeting_id +SQL nt:1rR => meeting/all_projection_ids:-> projection/meeting_id +SQL nt:1rR => meeting/projector_message_ids:-> projector_message/meeting_id +SQL nt:1rR => meeting/projector_countdown_ids:-> projector_countdown/meeting_id +SQL nt:1rR => meeting/tag_ids:-> tag/meeting_id +SQL nt:1rR => meeting/agenda_item_ids:-> agenda_item/meeting_id +SQL nt:1rR => meeting/list_of_speakers_ids:-> list_of_speakers/meeting_id +SQL nt:1rR => meeting/structure_level_list_of_speakers_ids:-> structure_level_list_of_speakers/meeting_id +SQL nt:1rR => meeting/point_of_order_category_ids:-> point_of_order_category/meeting_id +SQL nt:1rR => meeting/speaker_ids:-> speaker/meeting_id +SQL nt:1rR => meeting/topic_ids:-> topic/meeting_id +SQL nt:1rR => meeting/group_ids:-> group/meeting_id +SQL nt:1rR => meeting/meeting_mediafile_ids:-> meeting_mediafile/meeting_id +SQL nt:1GrR => meeting/mediafile_ids:-> mediafile/owner_id +SQL nt:1rR => meeting/motion_ids:-> motion/meeting_id +SQL nt:1r => meeting/forwarded_motion_ids:-> motion/origin_meeting_id +SQL nt:1rR => meeting/motion_comment_section_ids:-> motion_comment_section/meeting_id +SQL nt:1rR => meeting/motion_category_ids:-> motion_category/meeting_id +SQL nt:1rR => meeting/motion_block_ids:-> motion_block/meeting_id +SQL nt:1rR => meeting/motion_workflow_ids:-> motion_workflow/meeting_id +SQL nt:1rR => meeting/motion_comment_ids:-> motion_comment/meeting_id +SQL nt:1rR => meeting/motion_submitter_ids:-> motion_submitter/meeting_id +SQL nt:1rR => meeting/motion_supporter_ids:-> motion_supporter/meeting_id +SQL nt:1rR => meeting/motion_editor_ids:-> motion_editor/meeting_id +SQL nt:1rR => meeting/motion_working_group_speaker_ids:-> motion_working_group_speaker/meeting_id +SQL nt:1rR => meeting/motion_change_recommendation_ids:-> motion_change_recommendation/meeting_id +SQL nt:1rR => meeting/motion_state_ids:-> motion_state/meeting_id +SQL nr:1rR => meeting/poll_ids:-> poll/meeting_id +SQL nr:1rR => meeting/option_ids:-> option/meeting_id +SQL nr:1rR => meeting/vote_ids:-> vote/meeting_id +SQL nt:1rR => meeting/assignment_ids:-> assignment/meeting_id +SQL nt:1rR => meeting/assignment_candidate_ids:-> assignment_candidate/meeting_id +SQL nt:1rR => meeting/personal_note_ids:-> personal_note/meeting_id +SQL nt:1rR => meeting/chat_group_ids:-> chat_group/meeting_id +SQL nt:1rR => meeting/chat_message_ids:-> chat_message/meeting_id +SQL nt:1rR => meeting/structure_level_ids:-> structure_level/meeting_id +FIELD 1r:1t => meeting/logo_projector_main_id:-> meeting_mediafile/used_as_logo_projector_main_in_meeting_id +FIELD 1r:1t => meeting/logo_projector_header_id:-> meeting_mediafile/used_as_logo_projector_header_in_meeting_id +FIELD 1r:1t => meeting/logo_web_header_id:-> meeting_mediafile/used_as_logo_web_header_in_meeting_id +FIELD 1r:1t => meeting/logo_pdf_header_l_id:-> meeting_mediafile/used_as_logo_pdf_header_l_in_meeting_id +FIELD 1r:1t => meeting/logo_pdf_header_r_id:-> meeting_mediafile/used_as_logo_pdf_header_r_in_meeting_id +FIELD 1r:1t => meeting/logo_pdf_footer_l_id:-> meeting_mediafile/used_as_logo_pdf_footer_l_in_meeting_id +FIELD 1r:1t => meeting/logo_pdf_footer_r_id:-> meeting_mediafile/used_as_logo_pdf_footer_r_in_meeting_id +FIELD 1r:1t => meeting/logo_pdf_ballot_paper_id:-> meeting_mediafile/used_as_logo_pdf_ballot_paper_in_meeting_id +FIELD 1r:1t => meeting/font_regular_id:-> meeting_mediafile/used_as_font_regular_in_meeting_id +FIELD 1r:1t => meeting/font_italic_id:-> meeting_mediafile/used_as_font_italic_in_meeting_id +FIELD 1r:1t => meeting/font_bold_id:-> meeting_mediafile/used_as_font_bold_in_meeting_id +FIELD 1r:1t => meeting/font_bold_italic_id:-> meeting_mediafile/used_as_font_bold_italic_in_meeting_id +FIELD 1r:1t => meeting/font_monospace_id:-> meeting_mediafile/used_as_font_monospace_in_meeting_id +FIELD 1r:1t => meeting/font_chyron_speaker_name_id:-> meeting_mediafile/used_as_font_chyron_speaker_name_in_meeting_id +FIELD 1r:1t => meeting/font_projector_h1_id:-> meeting_mediafile/used_as_font_projector_h1_in_meeting_id +FIELD 1r:1t => meeting/font_projector_h2_id:-> meeting_mediafile/used_as_font_projector_h2_in_meeting_id +FIELD 1rR:nt => meeting/committee_id:-> committee/meeting_ids +SQL 1t:1r => meeting/default_meeting_for_committee_id:-> committee/default_meeting_id +SQL nt:nGt => meeting/organization_tag_ids:-> organization_tag/tagged_ids +SQL nt:nt => meeting/present_user_ids:-> user/is_present_in_meeting_ids +SQL nts:nts => meeting/user_ids:-> user/meeting_ids +FIELD 1rR:1t => meeting/reference_projector_id:-> projector/used_as_reference_projector_meeting_id +FIELD 1r:1t => meeting/list_of_speakers_countdown_id:-> projector_countdown/used_as_list_of_speakers_countdown_meeting_id +FIELD 1r:1t => meeting/poll_countdown_id:-> projector_countdown/used_as_poll_countdown_meeting_id +SQL nt:1GrR => meeting/projection_ids:-> projection/content_object_id +SQL ntR:1r => meeting/default_projector_agenda_item_list_ids:-> projector/used_as_default_projector_for_agenda_item_list_in_meeting_id +SQL ntR:1r => meeting/default_projector_topic_ids:-> projector/used_as_default_projector_for_topic_in_meeting_id +SQL ntR:1r => meeting/default_projector_list_of_speakers_ids:-> projector/used_as_default_projector_for_list_of_speakers_in_meeting_id +SQL ntR:1r => meeting/default_projector_current_los_ids:-> projector/used_as_default_projector_for_current_los_in_meeting_id +SQL ntR:1r => meeting/default_projector_motion_ids:-> projector/used_as_default_projector_for_motion_in_meeting_id +SQL ntR:1r => meeting/default_projector_amendment_ids:-> projector/used_as_default_projector_for_amendment_in_meeting_id +SQL ntR:1r => meeting/default_projector_motion_block_ids:-> projector/used_as_default_projector_for_motion_block_in_meeting_id +SQL ntR:1r => meeting/default_projector_assignment_ids:-> projector/used_as_default_projector_for_assignment_in_meeting_id +SQL ntR:1r => meeting/default_projector_mediafile_ids:-> projector/used_as_default_projector_for_mediafile_in_meeting_id +SQL ntR:1r => meeting/default_projector_message_ids:-> projector/used_as_default_projector_for_message_in_meeting_id +SQL ntR:1r => meeting/default_projector_countdown_ids:-> projector/used_as_default_projector_for_countdown_in_meeting_id +SQL ntR:1r => meeting/default_projector_assignment_poll_ids:-> projector/used_as_default_projector_for_assignment_poll_in_meeting_id +SQL ntR:1r => meeting/default_projector_motion_poll_ids:-> projector/used_as_default_projector_for_motion_poll_in_meeting_id +SQL ntR:1r => meeting/default_projector_poll_ids:-> projector/used_as_default_projector_for_poll_in_meeting_id +FIELD 1rR:1t => meeting/default_group_id:-> group/default_group_for_meeting_id +FIELD 1r:1t => meeting/admin_group_id:-> group/admin_group_for_meeting_id +FIELD 1r:1t => meeting/anonymous_group_id:-> group/anonymous_group_for_meeting_id +SQL nt:1r => meeting/relevant_history_entry_ids:-> history_entry/meeting_id + +FIELD 1rR:nt => meeting_mediafile/mediafile_id:-> mediafile/meeting_mediafile_ids +FIELD 1rR:nt => meeting_mediafile/meeting_id:-> meeting/meeting_mediafile_ids +SQL nt:nt => meeting_mediafile/inherited_access_group_ids:-> group/meeting_mediafile_inherited_access_group_ids +SQL nt:nt => meeting_mediafile/access_group_ids:-> group/meeting_mediafile_access_group_ids +SQL 1t:1GrR => meeting_mediafile/list_of_speakers_id:-> list_of_speakers/content_object_id +SQL nt:1GrR => meeting_mediafile/projection_ids:-> projection/content_object_id +SQL nGt:nt,nt,nt => meeting_mediafile/attachment_ids:-> motion/attachment_meeting_mediafile_ids,topic/attachment_meeting_mediafile_ids,assignment/attachment_meeting_mediafile_ids +SQL 1t:1r => meeting_mediafile/used_as_logo_projector_main_in_meeting_id:-> meeting/logo_projector_main_id +SQL 1t:1r => meeting_mediafile/used_as_logo_projector_header_in_meeting_id:-> meeting/logo_projector_header_id +SQL 1t:1r => meeting_mediafile/used_as_logo_web_header_in_meeting_id:-> meeting/logo_web_header_id +SQL 1t:1r => meeting_mediafile/used_as_logo_pdf_header_l_in_meeting_id:-> meeting/logo_pdf_header_l_id +SQL 1t:1r => meeting_mediafile/used_as_logo_pdf_header_r_in_meeting_id:-> meeting/logo_pdf_header_r_id +SQL 1t:1r => meeting_mediafile/used_as_logo_pdf_footer_l_in_meeting_id:-> meeting/logo_pdf_footer_l_id +SQL 1t:1r => meeting_mediafile/used_as_logo_pdf_footer_r_in_meeting_id:-> meeting/logo_pdf_footer_r_id +SQL 1t:1r => meeting_mediafile/used_as_logo_pdf_ballot_paper_in_meeting_id:-> meeting/logo_pdf_ballot_paper_id +SQL 1t:1r => meeting_mediafile/used_as_font_regular_in_meeting_id:-> meeting/font_regular_id +SQL 1t:1r => meeting_mediafile/used_as_font_italic_in_meeting_id:-> meeting/font_italic_id +SQL 1t:1r => meeting_mediafile/used_as_font_bold_in_meeting_id:-> meeting/font_bold_id +SQL 1t:1r => meeting_mediafile/used_as_font_bold_italic_in_meeting_id:-> meeting/font_bold_italic_id +SQL 1t:1r => meeting_mediafile/used_as_font_monospace_in_meeting_id:-> meeting/font_monospace_id +SQL 1t:1r => meeting_mediafile/used_as_font_chyron_speaker_name_in_meeting_id:-> meeting/font_chyron_speaker_name_id +SQL 1t:1r => meeting_mediafile/used_as_font_projector_h1_in_meeting_id:-> meeting/font_projector_h1_id +SQL 1t:1r => meeting_mediafile/used_as_font_projector_h2_in_meeting_id:-> meeting/font_projector_h2_id + +FIELD 1rR:nt => meeting_user/user_id:-> user/meeting_user_ids +FIELD 1rR:nt => meeting_user/meeting_id:-> meeting/meeting_user_ids +SQL nt:1rR => meeting_user/personal_note_ids:-> personal_note/meeting_user_id +SQL nt:1r => meeting_user/speaker_ids:-> speaker/meeting_user_id +SQL nt:1r => meeting_user/motion_supporter_ids:-> motion_supporter/meeting_user_id +SQL nt:1r => meeting_user/motion_editor_ids:-> motion_editor/meeting_user_id +SQL nt:1r => meeting_user/motion_working_group_speaker_ids:-> motion_working_group_speaker/meeting_user_id +SQL nt:1r => meeting_user/motion_submitter_ids:-> motion_submitter/meeting_user_id +SQL nt:1r => meeting_user/assignment_candidate_ids:-> assignment_candidate/meeting_user_id +FIELD 1r:nt => meeting_user/vote_delegated_to_id:-> meeting_user/vote_delegations_from_ids +SQL nt:1r => meeting_user/vote_delegations_from_ids:-> meeting_user/vote_delegated_to_id +SQL nt:1r => meeting_user/chat_message_ids:-> chat_message/meeting_user_id +SQL ntR:nt => meeting_user/group_ids:-> group/meeting_user_ids +SQL nt:nt => meeting_user/structure_level_ids:-> structure_level/meeting_user_ids + +FIELD 1r:nt => motion/lead_motion_id:-> motion/amendment_ids +SQL nt:1r => motion/amendment_ids:-> motion/lead_motion_id +FIELD 1r:nt => motion/sort_parent_id:-> motion/sort_child_ids +SQL nt:1r => motion/sort_child_ids:-> motion/sort_parent_id +FIELD 1r:nt => motion/origin_id:-> motion/derived_motion_ids +FIELD 1r:nt => motion/origin_meeting_id:-> meeting/forwarded_motion_ids +SQL nt:1r => motion/derived_motion_ids:-> motion/origin_id +SQL nt:nt => motion/all_origin_ids:-> motion/all_derived_motion_ids +SQL nt:nt => motion/all_derived_motion_ids:-> motion/all_origin_ids +SQL nt:nt => motion/identical_motion_ids:-> motion/identical_motion_ids +FIELD 1rR:nt => motion/state_id:-> motion_state/motion_ids +FIELD 1r:nt => motion/recommendation_id:-> motion_state/motion_recommendation_ids +SQL nGt:nt => motion/state_extension_reference_ids:-> motion/referenced_in_motion_state_extension_ids +SQL nt:nGt => motion/referenced_in_motion_state_extension_ids:-> motion/state_extension_reference_ids +SQL nGt:nt => motion/recommendation_extension_reference_ids:-> motion/referenced_in_motion_recommendation_extension_ids +SQL nt:nGt => motion/referenced_in_motion_recommendation_extension_ids:-> motion/recommendation_extension_reference_ids +FIELD 1r:nt => motion/category_id:-> motion_category/motion_ids +FIELD 1r:nt => motion/block_id:-> motion_block/motion_ids +SQL nt:1rR => motion/submitter_ids:-> motion_submitter/motion_id +SQL nt:1rR => motion/supporter_ids:-> motion_supporter/motion_id +SQL nt:1rR => motion/editor_ids:-> motion_editor/motion_id +SQL nt:1rR => motion/working_group_speaker_ids:-> motion_working_group_speaker/motion_id +SQL nt:1GrR => motion/poll_ids:-> poll/content_object_id +SQL nr:1Gr => motion/option_ids:-> option/content_object_id +SQL nt:1rR => motion/change_recommendation_ids:-> motion_change_recommendation/motion_id +SQL nt:1rR => motion/comment_ids:-> motion_comment/motion_id +SQL 1t:1GrR => motion/agenda_item_id:-> agenda_item/content_object_id +SQL 1tR:1GrR => motion/list_of_speakers_id:-> list_of_speakers/content_object_id +SQL nt:nGt => motion/tag_ids:-> tag/tagged_ids +SQL nt:nGt => motion/attachment_meeting_mediafile_ids:-> meeting_mediafile/attachment_ids +SQL nt:1GrR => motion/projection_ids:-> projection/content_object_id +SQL nt:1Gr => motion/personal_note_ids:-> personal_note/content_object_id +FIELD 1rR:nt => motion/meeting_id:-> meeting/motion_ids +SQL nt:1Gr => motion/history_entry_ids:-> history_entry/model_id + +SQL nt:1r => motion_block/motion_ids:-> motion/block_id +SQL 1t:1GrR => motion_block/agenda_item_id:-> agenda_item/content_object_id +SQL 1tR:1GrR => motion_block/list_of_speakers_id:-> list_of_speakers/content_object_id +SQL nt:1GrR => motion_block/projection_ids:-> projection/content_object_id +FIELD 1rR:nt => motion_block/meeting_id:-> meeting/motion_block_ids + +FIELD 1r:nt => motion_category/parent_id:-> motion_category/child_ids +SQL nt:1r => motion_category/child_ids:-> motion_category/parent_id +SQL nt:1r => motion_category/motion_ids:-> motion/category_id +FIELD 1rR:nt => motion_category/meeting_id:-> meeting/motion_category_ids + +FIELD 1rR:nt => motion_change_recommendation/motion_id:-> motion/change_recommendation_ids +FIELD 1rR:nt => motion_change_recommendation/meeting_id:-> meeting/motion_change_recommendation_ids + +FIELD 1rR:nt => motion_comment/motion_id:-> motion/comment_ids +FIELD 1rR:nt => motion_comment/section_id:-> motion_comment_section/comment_ids +FIELD 1rR:nt => motion_comment/meeting_id:-> meeting/motion_comment_ids + +SQL nt:1rR => motion_comment_section/comment_ids:-> motion_comment/section_id +SQL nt:nt => motion_comment_section/read_group_ids:-> group/read_comment_section_ids +SQL nt:nt => motion_comment_section/write_group_ids:-> group/write_comment_section_ids +FIELD 1rR:nt => motion_comment_section/meeting_id:-> meeting/motion_comment_section_ids + +FIELD 1r:nt => motion_editor/meeting_user_id:-> meeting_user/motion_editor_ids +FIELD 1rR:nt => motion_editor/motion_id:-> motion/editor_ids +FIELD 1rR:nt => motion_editor/meeting_id:-> meeting/motion_editor_ids + +FIELD 1r:nt => motion_state/submitter_withdraw_state_id:-> motion_state/submitter_withdraw_back_ids +SQL nt:1r => motion_state/submitter_withdraw_back_ids:-> motion_state/submitter_withdraw_state_id +SQL nt:nt => motion_state/next_state_ids:-> motion_state/previous_state_ids +SQL nt:nt => motion_state/previous_state_ids:-> motion_state/next_state_ids +SQL nt:1rR => motion_state/motion_ids:-> motion/state_id +SQL nt:1r => motion_state/motion_recommendation_ids:-> motion/recommendation_id +FIELD 1rR:nt => motion_state/workflow_id:-> motion_workflow/state_ids +SQL 1t:1rR => motion_state/first_state_of_workflow_id:-> motion_workflow/first_state_id +FIELD 1rR:nt => motion_state/meeting_id:-> meeting/motion_state_ids + +FIELD 1r:nt => motion_submitter/meeting_user_id:-> meeting_user/motion_submitter_ids +FIELD 1rR:nt => motion_submitter/motion_id:-> motion/submitter_ids +FIELD 1rR:nt => motion_submitter/meeting_id:-> meeting/motion_submitter_ids + +FIELD 1r:nt => motion_supporter/meeting_user_id:-> meeting_user/motion_supporter_ids +FIELD 1rR:nt => motion_supporter/motion_id:-> motion/supporter_ids +FIELD 1rR:nt => motion_supporter/meeting_id:-> meeting/motion_supporter_ids + +SQL nt:1rR => motion_workflow/state_ids:-> motion_state/workflow_id +FIELD 1rR:1t => motion_workflow/first_state_id:-> motion_state/first_state_of_workflow_id +SQL 1t:1rR => motion_workflow/default_workflow_meeting_id:-> meeting/motions_default_workflow_id +SQL 1t:1rR => motion_workflow/default_amendment_workflow_meeting_id:-> meeting/motions_default_amendment_workflow_id +FIELD 1rR:nt => motion_workflow/meeting_id:-> meeting/motion_workflow_ids + +FIELD 1r:nt => motion_working_group_speaker/meeting_user_id:-> meeting_user/motion_working_group_speaker_ids +FIELD 1rR:nt => motion_working_group_speaker/motion_id:-> motion/working_group_speaker_ids +FIELD 1rR:nt => motion_working_group_speaker/meeting_id:-> meeting/motion_working_group_speaker_ids + +FIELD 1r:nr => option/poll_id:-> poll/option_ids +FIELD 1r:1r => option/used_as_global_option_in_poll_id:-> poll/global_option_id +SQL nr:1rR => option/vote_ids:-> vote/option_id +FIELD 1Gr:nr,nr,1tR => option/content_object_id:-> motion/option_ids,user/option_ids,poll_candidate_list/option_id +FIELD 1rR:nr => option/meeting_id:-> meeting/option_ids + +SQL nr:1rR => organization/gender_ids:-> gender/organization_id +SQL nr:1rR => organization/committee_ids:-> committee/organization_id +SQL nt:1r => organization/active_meeting_ids:-> meeting/is_active_in_organization_id +SQL nt:1r => organization/archived_meeting_ids:-> meeting/is_archived_in_organization_id +SQL nt:1r => organization/template_meeting_ids:-> meeting/template_for_organization_id +SQL nr:1rR => organization/organization_tag_ids:-> organization_tag/organization_id +FIELD 1rR:1t => organization/theme_id:-> theme/theme_for_organization_id +SQL nr:1rR => organization/theme_ids:-> theme/organization_id +SQL nt:1GrR => organization/mediafile_ids:-> mediafile/owner_id +SQL nt:1r => organization/published_mediafile_ids:-> mediafile/published_to_meetings_in_organization_id +SQL nr:1rR => organization/user_ids:-> user/organization_id + +SQL nGt:nt,nt => organization_tag/tagged_ids:-> committee/organization_tag_ids,meeting/organization_tag_ids + +FIELD 1rR:nt => personal_note/meeting_user_id:-> meeting_user/personal_note_ids +FIELD 1Gr:nt => personal_note/content_object_id:-> motion/personal_note_ids +FIELD 1rR:nt => personal_note/meeting_id:-> meeting/personal_note_ids + +FIELD 1rR:nt => point_of_order_category/meeting_id:-> meeting/point_of_order_category_ids +SQL nt:1r => point_of_order_category/speaker_ids:-> speaker/point_of_order_category_id + +FIELD 1GrR:nt,nt,nt => poll/content_object_id:-> motion/poll_ids,assignment/poll_ids,topic/poll_ids +SQL nr:1r => poll/option_ids:-> option/poll_id +FIELD 1r:1r => poll/global_option_id:-> option/used_as_global_option_in_poll_id +SQL nt:nt => poll/voted_ids:-> user/poll_voted_ids +SQL nt:nt => poll/entitled_group_ids:-> group/poll_ids +SQL nt:1GrR => poll/projection_ids:-> projection/content_object_id +FIELD 1rR:nr => poll/meeting_id:-> meeting/poll_ids + +FIELD 1rR:nr => poll_candidate/poll_candidate_list_id:-> poll_candidate_list/poll_candidate_ids +FIELD 1r:nr => poll_candidate/user_id:-> user/poll_candidate_ids +FIELD 1rR:nr => poll_candidate/meeting_id:-> meeting/poll_candidate_ids + +SQL nr:1rR => poll_candidate_list/poll_candidate_ids:-> poll_candidate/poll_candidate_list_id +FIELD 1rR:nr => poll_candidate_list/meeting_id:-> meeting/poll_candidate_list_ids +SQL 1tR:1Gr => poll_candidate_list/option_id:-> option/content_object_id + +FIELD 1r:nt => projection/current_projector_id:-> projector/current_projection_ids +FIELD 1r:nt => projection/preview_projector_id:-> projector/preview_projection_ids +FIELD 1r:nt => projection/history_projector_id:-> projector/history_projection_ids +FIELD 1GrR:nt,nt,nt,nt,nt,nt,nt,nt,nt,nt,nt => projection/content_object_id:-> meeting/projection_ids,motion/projection_ids,meeting_mediafile/projection_ids,list_of_speakers/projection_ids,motion_block/projection_ids,assignment/projection_ids,agenda_item/projection_ids,topic/projection_ids,poll/projection_ids,projector_message/projection_ids,projector_countdown/projection_ids +FIELD 1rR:nt => projection/meeting_id:-> meeting/all_projection_ids + +SQL nt:1r => projector/current_projection_ids:-> projection/current_projector_id +SQL nt:1r => projector/preview_projection_ids:-> projection/preview_projector_id +SQL nt:1r => projector/history_projection_ids:-> projection/history_projector_id +SQL 1t:1rR => projector/used_as_reference_projector_meeting_id:-> meeting/reference_projector_id +FIELD 1r:ntR => projector/used_as_default_projector_for_agenda_item_list_in_meeting_id:-> meeting/default_projector_agenda_item_list_ids +FIELD 1r:ntR => projector/used_as_default_projector_for_topic_in_meeting_id:-> meeting/default_projector_topic_ids +FIELD 1r:ntR => projector/used_as_default_projector_for_list_of_speakers_in_meeting_id:-> meeting/default_projector_list_of_speakers_ids +FIELD 1r:ntR => projector/used_as_default_projector_for_current_los_in_meeting_id:-> meeting/default_projector_current_los_ids +FIELD 1r:ntR => projector/used_as_default_projector_for_motion_in_meeting_id:-> meeting/default_projector_motion_ids +FIELD 1r:ntR => projector/used_as_default_projector_for_amendment_in_meeting_id:-> meeting/default_projector_amendment_ids +FIELD 1r:ntR => projector/used_as_default_projector_for_motion_block_in_meeting_id:-> meeting/default_projector_motion_block_ids +FIELD 1r:ntR => projector/used_as_default_projector_for_assignment_in_meeting_id:-> meeting/default_projector_assignment_ids +FIELD 1r:ntR => projector/used_as_default_projector_for_mediafile_in_meeting_id:-> meeting/default_projector_mediafile_ids +FIELD 1r:ntR => projector/used_as_default_projector_for_message_in_meeting_id:-> meeting/default_projector_message_ids +FIELD 1r:ntR => projector/used_as_default_projector_for_countdown_in_meeting_id:-> meeting/default_projector_countdown_ids +FIELD 1r:ntR => projector/used_as_default_projector_for_assignment_poll_in_meeting_id:-> meeting/default_projector_assignment_poll_ids +FIELD 1r:ntR => projector/used_as_default_projector_for_motion_poll_in_meeting_id:-> meeting/default_projector_motion_poll_ids +FIELD 1r:ntR => projector/used_as_default_projector_for_poll_in_meeting_id:-> meeting/default_projector_poll_ids +FIELD 1rR:nt => projector/meeting_id:-> meeting/projector_ids + +SQL nt:1GrR => projector_countdown/projection_ids:-> projection/content_object_id +SQL 1t:1r => projector_countdown/used_as_list_of_speakers_countdown_meeting_id:-> meeting/list_of_speakers_countdown_id +SQL 1t:1r => projector_countdown/used_as_poll_countdown_meeting_id:-> meeting/poll_countdown_id +FIELD 1rR:nt => projector_countdown/meeting_id:-> meeting/projector_countdown_ids + +SQL nt:1GrR => projector_message/projection_ids:-> projection/content_object_id +FIELD 1rR:nt => projector_message/meeting_id:-> meeting/projector_message_ids + +FIELD 1rR:nt => speaker/list_of_speakers_id:-> list_of_speakers/speaker_ids +FIELD 1r:nt => speaker/structure_level_list_of_speakers_id:-> structure_level_list_of_speakers/speaker_ids +FIELD 1r:nt => speaker/meeting_user_id:-> meeting_user/speaker_ids +FIELD 1r:nt => speaker/point_of_order_category_id:-> point_of_order_category/speaker_ids +FIELD 1rR:nt => speaker/meeting_id:-> meeting/speaker_ids + +SQL nt:nt => structure_level/meeting_user_ids:-> meeting_user/structure_level_ids +SQL nt:1rR => structure_level/structure_level_list_of_speakers_ids:-> structure_level_list_of_speakers/structure_level_id +FIELD 1rR:nt => structure_level/meeting_id:-> meeting/structure_level_ids + +FIELD 1rR:nt => structure_level_list_of_speakers/structure_level_id:-> structure_level/structure_level_list_of_speakers_ids +FIELD 1rR:nt => structure_level_list_of_speakers/list_of_speakers_id:-> list_of_speakers/structure_level_list_of_speakers_ids +SQL nt:1r => structure_level_list_of_speakers/speaker_ids:-> speaker/structure_level_list_of_speakers_id +FIELD 1rR:nt => structure_level_list_of_speakers/meeting_id:-> meeting/structure_level_list_of_speakers_ids + +SQL nGt:nt,nt,nt => tag/tagged_ids:-> agenda_item/tag_ids,assignment/tag_ids,motion/tag_ids +FIELD 1rR:nt => tag/meeting_id:-> meeting/tag_ids + +SQL 1t:1rR => theme/theme_for_organization_id:-> organization/theme_id + +SQL nt:nGt => topic/attachment_meeting_mediafile_ids:-> meeting_mediafile/attachment_ids +SQL 1tR:1GrR => topic/agenda_item_id:-> agenda_item/content_object_id +SQL 1tR:1GrR => topic/list_of_speakers_id:-> list_of_speakers/content_object_id +SQL nt:1GrR => topic/poll_ids:-> poll/content_object_id +SQL nt:1GrR => topic/projection_ids:-> projection/content_object_id +FIELD 1rR:nt => topic/meeting_id:-> meeting/topic_ids + +FIELD 1r:nr => user/gender_id:-> gender/user_ids +SQL nt:nt => user/is_present_in_meeting_ids:-> meeting/present_user_ids +SQL nts:nts => user/committee_ids:-> committee/user_ids +SQL nt:nt => user/committee_management_ids:-> committee/manager_ids +SQL nt:1rR => user/meeting_user_ids:-> meeting_user/user_id +SQL nt:nt => user/poll_voted_ids:-> poll/voted_ids +SQL nr:1Gr => user/option_ids:-> option/content_object_id +SQL nr:1r => user/vote_ids:-> vote/user_id +SQL nr:1r => user/delegated_vote_ids:-> vote/delegated_user_id +SQL nr:1r => user/poll_candidate_ids:-> poll_candidate/user_id +FIELD 1r:nt => user/home_committee_id:-> committee/native_user_ids +SQL nt:1r => user/history_position_ids:-> history_position/user_id +SQL nt:1Gr => user/history_entry_ids:-> history_entry/model_id +SQL nts:nts => user/meeting_ids:-> meeting/user_ids + +FIELD 1rR:nr => vote/option_id:-> option/vote_ids +FIELD 1r:nr => vote/user_id:-> user/vote_ids +FIELD 1r:nr => vote/delegated_user_id:-> user/delegated_vote_ids +FIELD 1rR:nr => vote/meeting_id:-> meeting/vote_ids + +*/ +/* +There are 2 errors/warnings + poll/live_votes: type:JSON is marked as a calculated field and not generated in schema + projection/content: type:JSON is marked as a calculated field and not generated in schema +*/ + +/* Missing attribute handling for constant, equal_fields, on_delete, deferred */ \ No newline at end of file diff --git a/dev/sql/test_data.sql b/dev/sql/test_data.sql new file mode 100644 index 00000000..1ee8c943 --- /dev/null +++ b/dev/sql/test_data.sql @@ -0,0 +1,103 @@ +-- This script should only be used for an database filled with base_data.sql. + +--relation --relation-list gender_ids +INSERT INTO user_t (username, gender_id) VALUES ('tom', 1); + +BEGIN; + +INSERT INTO motion_state_t (id, name, weight, workflow_id, meeting_id) +VALUES (5, 'motionState5', 1, 2, 2); +SELECT nextval('motion_state_t_id_seq'); + +INSERT INTO motion_workflow_t ( + id, name, first_state_id, meeting_id +) +VALUES (2, 'workflow2', 4, 2); +SELECT nextval('motion_workflow_t_id_seq'); + +INSERT INTO meeting_t ( + id, + name, + motions_default_workflow_id, + motions_default_amendment_workflow_id, + committee_id, + reference_projector_id, + default_group_id +) +VALUES (2, 'name', 2, 2, 2, 2, 3); +SELECT nextval('meeting_t_id_seq'); + + +--generic-relation-list tagged_ids +INSERT INTO organization_tag_t (id, name, color) +VALUES (1, 'tagA', '#cc3b03'); +SELECT nextval('organization_tag_t_id_seq'); + +--relation-list organization_tag_ids --relation 1:1 default_meeting_id +INSERT INTO committee_t (id, name, default_meeting_id) +VALUES (2, 'plenum', 2); +SELECT nextval('committee_t_id_seq'); + +INSERT INTO projector_t ( + id, + meeting_id, + used_as_default_projector_for_agenda_item_list_in_meeting_id, + used_as_default_projector_for_topic_in_meeting_id, + used_as_default_projector_for_list_of_speakers_in_meeting_id, + used_as_default_projector_for_current_los_in_meeting_id, + used_as_default_projector_for_motion_in_meeting_id, + used_as_default_projector_for_amendment_in_meeting_id, + used_as_default_projector_for_motion_block_in_meeting_id, + used_as_default_projector_for_assignment_in_meeting_id, + used_as_default_projector_for_mediafile_in_meeting_id, + used_as_default_projector_for_message_in_meeting_id, + used_as_default_projector_for_countdown_in_meeting_id, + used_as_default_projector_for_assignment_poll_in_meeting_id, + used_as_default_projector_for_motion_poll_in_meeting_id, + used_as_default_projector_for_poll_in_meeting_id +) +VALUES (2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2); +SELECT nextval('projector_t_id_seq'); + +INSERT INTO projector_t (id, meeting_id) +VALUES (3, 2); +SELECT nextval('projector_t_id_seq'); + +INSERT INTO group_t (id, name, meeting_id) +VALUES (3, 'gruppe3', 2); +SELECT nextval('group_t_id_seq'); + +COMMIT; + +INSERT INTO organization_tag_t (id, name, color) +VALUES (2, 'bunt', '#ffffff'); +SELECT nextval('organization_tag_t_id_seq'); + +INSERT INTO gm_organization_tag_tagged_ids_t (organization_tag_id, tagged_id) +VALUES (2, 'meeting/1'); + +BEGIN; +INSERT INTO topic_t (id, title, meeting_id) +VALUES (1, 'Thema1', 2); +SELECT nextval('topic_t_id_seq'); + +--list_of_speakers.content_object_id:topic.list_of_speakers_id gr:r +INSERT INTO list_of_speakers_t ( + id, content_object_id, sequential_number, meeting_id +) +VALUES (1, 'topic/1', 1, 2); + +--agenda_item.content_object_id:topic.agenda_item_id gr:r +INSERT INTO agenda_item_t (content_object_id, meeting_id) +VALUES ('topic/1', 2); +COMMIT; + +--rl:gr organization.mediafile_ids:mediafile.owner_id +INSERT INTO mediafile_t (id, owner_id) +VALUES (1, 'organization/1'); + +--rl:rl committee_ids:user_ids +INSERT INTO nm_committee_manager_ids_user_t (committee_id, user_id) +VALUES (1, 1); + +COMMIT; diff --git a/dev/src/__init__.py b/dev/src/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/dev/src/db_utils.py b/dev/src/db_utils.py new file mode 100644 index 00000000..0a256939 --- /dev/null +++ b/dev/src/db_utils.py @@ -0,0 +1,36 @@ +from typing import Any + +from .python_sql import Column, Table + + +class DbUtils: + @classmethod + def get_columns_and_values_for_insert( + cls, + table: Table, + data_list: list[dict[str, Any]], + ) -> tuple[list[Column], list[list[Any | None]]]: + """ + takes a list of dicts, each one to be inserted + Takes care of columns and row positions and fills + not existent columns in row with "None" + """ + if not data_list: + return [], [] + # use all keys in same sequence + keys_set: set = set() + for data in data_list: + keys_set.update(data.keys()) + keys: list = sorted(keys_set) + columns = [Column(table, key) for key in keys] + values = [[row.get(k, None) for k in keys] for row in data_list] + return columns, values + + @classmethod + def get_columns_from_list(cls, table: Table, items: list[str]) -> list[Column]: + return [Column(table, item) for item in items] + + @classmethod + def get_pg_array_for_cu(cls, data: list) -> str: + """converts a value list into string used for complete array field""" + return f"""{{"{'","'.join(item for item in data)}"}}""" diff --git a/dev/src/generate_sql_schema.py b/dev/src/generate_sql_schema.py new file mode 100644 index 00000000..096c7ec3 --- /dev/null +++ b/dev/src/generate_sql_schema.py @@ -0,0 +1,1801 @@ +import string +import sys +from collections import defaultdict +from collections.abc import Callable +from decimal import Decimal +from enum import Enum +from pathlib import Path +from string import Formatter +from textwrap import dedent, indent +from typing import Any, TypedDict, cast + +from sqlfluff import fix + +from .helper_get_names import ( + KEYSEPARATOR, + FieldSqlErrorType, + HelperGetNames, + InternalHelper, + TableFieldType, +) + +SOURCE = (Path(__file__).parent / ".." / ".." / "models.yml").resolve() +DESTINATION = (Path(__file__).parent / ".." / "sql" / "schema_relational.sql").resolve() +MODELS: dict[str, dict[str, Any]] = {} + + +class SchemaZoneTexts(TypedDict, total=False): + """TypedDict definition for generation of different sql-code parts""" + + table: str + view: str + post_view: str + alter_table: str + alter_table_final: str + create_trigger_partitioned_sequences: str + create_trigger_1_1_relation_not_null: str + create_trigger_1_n_relation_not_null: str + create_trigger_n_m_relation_not_null: str + create_trigger_unique_ids_pair_code: str + create_trigger_notify: str + undecided: str + final_info: str + errors: list[str] + + +class SQL_Delete_Update_Options(str, Enum): + RESTRICT = "RESTRICT" + CASCADE = "CASCADE" + SET_NULL = "SET NULL" + SET_DEFAULT = "SET DEFAULT" + NO_ACTION = "NO ACTION" + + +class SubstDict(TypedDict, total=False): + """dict for substitutions of field templates""" + + field_name: str + type: str + primary_key: str + required: str + default: str + minimum: str + minLength: str + deferred: str + check_enum: str + unique: str + + +class GenerateCodeBlocks: + """Main work is done here by recursing the models and their fields and determine the method to use""" + + if not InternalHelper.MODELS: + InternalHelper.read_models_yml(SOURCE.as_posix()) + intermediate_tables: dict[str, str] = ( + {} + ) # Key=Name, data: collected content of table + + @classmethod + def generate_the_code( + cls, + ) -> tuple[ + str, str, str, str, str, list[str], str, str, str, str, str, str, str, list[str] + ]: + """ + Return values: + pre_code: Type definitions, generated trigger definitions etc., which should all appear before first table definitions + table_name_code: All table definitions + view_name_code: All view definitions, after all views, because of view field definition by sql + alter_table_final_code: Changes on tables defining relations after, which should appear after all table/views definition to be sequence independant + final_info_code: Detailed info about all relation fields.Types: relation, relation-list, generic-relation and generic-relation-list + missing_handled_atributes: List of unhandled attributes. handled one's are to be set manually. + im_table_code: Code for intermediate tables. + n:m-relations name schema: f"nm_{smaller-table-name}_{it's-fieldname}_{greater-table_name}" uses one per relation + g:m-relations name schema: f"gm_{table_field.table}_{table_field.column}" of table with generic-list-field + create_trigger_partitioned_sequences_code: Definitions of triggers calling generate_sequence + create_trigger_1_1_relation_not_null_code: Definitions of triggers calling check_not_null_for_1_1_relation + create_trigger_1_n_relation_not_null_code: Definitions of triggers calling check_not_null_for_1_n + create_trigger_n_m_relation_not_null_code: Definitions of triggers calling check_not_null_for_n_m + create_trigger_unique_ids_pair_code: Definitions of triggers calling check_unique_ids_pair + create_trigger_notify_code: Definitions of triggers calling notify_modified_models + errors: to show + """ + handled_attributes = { + "required", + "maxLength", + "minLength", + "default", + "type", + "restriction_mode", + "minimum", + "calculated", + "description", + "read_only", + "enum", + "items", + "to", + "reference", + "sequence_scope", + # "on_delete", # must have other name then the key-value-store one + "sql", + # "equal_fields", # Seems we need, see example_transactional.sql between meeting and groups? + # "unique", # TODO: still to design + } + pre_code: str = "" + table_name_code: str = "" + view_name_code: str = "" + alter_table_final_code: str = "" + create_trigger_partitioned_sequences_code: str = "" + create_trigger_1_1_relation_not_null_code: str = "" + create_trigger_1_n_relation_not_null_code: str = "" + create_trigger_n_m_relation_not_null_code: str = "" + create_trigger_unique_ids_pair_code: str = "" + create_trigger_notify_code: str = "" + final_info_code: str = "" + missing_handled_attributes = [] + im_table_code = "" + errors: list[str] = [] + + for type_ in ["1_1", "1_n", "n_m"]: + pre_code += Helper.NOT_NULL_TRIGGER_FUNCTION_TEMPLATE.substitute( + cls.get_not_null_trigger_params(type_) + ) + + for table_name, fields in InternalHelper.MODELS.items(): + if table_name in ["_migration_index", "_meta"]: + continue + + schema_zone_texts = cast(SchemaZoneTexts, defaultdict(str)) + cls.intermediate_tables = {} + + for fname, fdata in fields.items(): + for attr in fdata: + if ( + attr not in handled_attributes + and attr not in missing_handled_attributes + ): + missing_handled_attributes.append(attr) + method_or_str, type_ = cls.get_method(fname, fdata) + if isinstance(method_or_str, str): + error = Helper.prefix_error(method_or_str, table_name, fname) + schema_zone_texts["undecided"] += error + errors.append(error) + else: + result, error = method_or_str(table_name, fname, fdata, type_) + for k, v in result.items(): + schema_zone_texts[k] += v or "" # type: ignore + if error: + errors.append(Helper.prefix_error(error, table_name, fname)) + + if code := schema_zone_texts["table"]: + table_name_code += Helper.get_table_head(table_name) + table_name_code += Helper.get_table_body_end(code) + "\n\n" + if code := schema_zone_texts["alter_table"]: + table_name_code += code + "\n" + if code := schema_zone_texts["undecided"]: + table_name_code += Helper.get_undecided_all(table_name, code) + view_name_code += Helper.get_view_head(table_name) + view_name_code += Helper.get_view_body_end( + table_name, schema_zone_texts.get("view", "") + ) + if code := schema_zone_texts["post_view"]: + view_name_code += code + if code := schema_zone_texts["alter_table_final"]: + alter_table_final_code += code + "\n" + if code := schema_zone_texts["create_trigger_partitioned_sequences"]: + create_trigger_partitioned_sequences_code += code + "\n" + if code := schema_zone_texts["create_trigger_1_1_relation_not_null"]: + create_trigger_1_1_relation_not_null_code += code + "\n" + if code := schema_zone_texts["create_trigger_1_n_relation_not_null"]: + create_trigger_1_n_relation_not_null_code += code + "\n" + if code := schema_zone_texts["create_trigger_n_m_relation_not_null"]: + create_trigger_n_m_relation_not_null_code += code + "\n" + if code := schema_zone_texts["create_trigger_unique_ids_pair_code"]: + create_trigger_unique_ids_pair_code += code + "\n" + if code := schema_zone_texts["final_info"]: + final_info_code += code + "\n" + for im_table in cls.intermediate_tables.values(): + im_table_code += im_table + + # schema_zone_texts is filled per model field. + # If any fields for this collection generated table code, create the main notify trigger on it. + if schema_zone_texts["table"]: + create_trigger_notify_code += ( + Helper.get_notify_trigger(table_name) + "\n" + ) + # Special triggers (e.g. for relation fields) come after + # TODO: needs to be filled in the get_*_relation_*_type functions + if code := schema_zone_texts["create_trigger_notify"]: + create_trigger_notify_code += code + "\n" + + return ( + pre_code, + table_name_code, + view_name_code, + alter_table_final_code, + final_info_code, + missing_handled_attributes, + im_table_code, + create_trigger_partitioned_sequences_code, + create_trigger_1_1_relation_not_null_code, + create_trigger_1_n_relation_not_null_code, + create_trigger_n_m_relation_not_null_code, + create_trigger_unique_ids_pair_code, + create_trigger_notify_code, + errors, + ) + + @staticmethod + def get_not_null_trigger_params(type_: str) -> dict[str, str]: + if type_ == "1_1": + docstring = dedent( + """\ + -- Parameters required for all operation types + -- 0. own_collection – name of the view on which the trigger is defined + -- 1. own_column – column in `own_table` referencing + -- `foreign_table` + -- + -- Parameter needed for extended error message generation for 'UPDATE' and + -- 'DELETE' (can be empty on INSERT) + -- 2. foreign_collection – name of collection of the triggered table that + -- will be used to SELECT + -- 3. foreign_column – column in the foreign table referencing + -- `own_table`""" + ) + parameters_declaration = indent( + dedent( + """\ + -- Parameters from TRIGGER DEFINITION + -- Always required + own_collection TEXT := TG_ARGV[0]; + own_column TEXT := TG_ARGV[1]; + + -- Only for TG_OP in ('UPDATE', 'DELETE') + foreign_collection TEXT := TG_ARGV[2]; + foreign_column TEXT := TG_ARGV[3]; + + -- Calculated parameters + own_id INTEGER; + foreign_id INTEGER; + counted INTEGER; + error_message TEXT;""" + ), + " ", + ) + select_expression = "EXECUTE format('SELECT %I FROM %I WHERE id = %L', own_column, own_collection, own_id) INTO counted;" + + elif type_ == "1_n": + docstring = dedent( + """\ + -- Parameters required for all operation types + -- 0. own_table – name of the table on which the trigger is defined + -- 1. own_column – column in `own_table` referencing + -- `foreign_table` + -- 2. foreign_table – name of the triggered table, that will be used to SELECT + -- 3. foreign_column – column in the foreign table referencing + -- `own_table`""" + ) + parameters_declaration = indent( + dedent( + """\ + -- Parameters from TRIGGER DEFINITION + -- Always required + own_table TEXT := TG_ARGV[0]; + own_column TEXT := TG_ARGV[1]; + foreign_table TEXT := TG_ARGV[2]; + foreign_column TEXT := TG_ARGV[3]; + + -- Calculated parameters + own_collection TEXT; + foreign_collection TEXT; + own_id INTEGER; + foreign_id INTEGER; + counted INTEGER; + error_message TEXT;""" + ), + " ", + ) + select_expression = "EXECUTE format('SELECT 1 FROM %I WHERE %I = %L', foreign_table, foreign_column, own_id) INTO counted;" + + else: + docstring = dedent( + """\ + -- Parameters required for both INSERT and DELETE operations + -- 0. intermediate_table_name – name of the n:m table + -- 1. own_table – name of the table on which the trigger is defined + -- 2. own_column – column in `own_table` referencing + -- `foreign_collection` + -- 3. intermediate_table_own_key – column in the n:m table referencing + -- `own_table` + -- + -- Parameters needed for extended error message generation for 'DELETE' + -- (can be empty on INSERT) + -- 4. intermediate_table_foreign_key – column in the n:m table referencing + -- the foreign table + -- 5. foreign_collection – name of the collection of the foreign table + -- 6. foreign_column – column in the foreign table referencing + -- `own_collection`""" + ) + parameters_declaration = indent( + dedent( + """\ + -- Parameters from TRIGGER DEFINITION + -- Always required + intermediate_table_name TEXT := TG_ARGV[0]; + own_table TEXT := TG_ARGV[1]; + own_column TEXT := TG_ARGV[2]; + intermediate_table_own_key TEXT := TG_ARGV[3]; + + -- Only for TG_OP = 'DELETE' + intermediate_table_foreign_key TEXT := TG_ARGV[4]; + foreign_collection TEXT := TG_ARGV[5]; + foreign_column TEXT := TG_ARGV[6]; + + -- Calculated parameters + own_collection TEXT; + own_id INTEGER; + foreign_id INTEGER; + counted INTEGER; + error_message TEXT;""" + ), + " ", + ) + select_expression = "EXECUTE format('SELECT 1 FROM %I WHERE %I = %L', intermediate_table_name, intermediate_table_own_key, own_id) INTO counted;" + + return { + "trigger_type": type_, + "docstring": docstring, + "parameters_declaration": parameters_declaration, + "foreign_column": ( + "intermediate_table_own_key" if type_ == "n_m" else "foreign_column" + ), + "query_relation": "own_collection" if type_ == "1_1" else "own_table", + "select_expression": select_expression, + "own_collection_definition": ( + "" + if type_ == "1_1" + else f"\n {Helper.COLLECTION_FROM_TABLE_TEMPLATE.substitute({'parameter': 'own_collection', 'table_t': 'own_table'})}" + ), + "ud_operations_filter": ( + "(TG_OP = 'DELETE')" + if type_ == "n_m" + else "TG_OP IN ('UPDATE', 'DELETE')" + ), + "foreign_collection_definition": ( + f"\n {Helper.COLLECTION_FROM_TABLE_TEMPLATE.substitute({'parameter': 'foreign_collection', 'table_t': 'foreign_table'})}" + if type_ == "1_n" + else "" + ), + "foreign_id": ( + "hstore(OLD) -> intermediate_table_foreign_key" + if type_ == "n_m" + else "OLD.id" + ), + } + + @classmethod + def get_method( + cls, fname: str, fdata: dict[str, Any] + ) -> tuple[str | Callable[..., tuple[SchemaZoneTexts, str]], str]: + """ + returns + - string or a callable with return value of type SchemaZoneTexts + - type as string + """ + if fdata.get("calculated"): + return ( + f"type:{fdata.get('type')} is marked as a calculated field and not generated in schema\n", + "", + ) + if fname == "id": + type_ = "primary_key" + elif ( + fname == "organization_id" + ): # temporary, just to fill the 4 organization_id-fields automatically with 1 + type_ = "organization_id" + else: + type_ = fdata.get("type", "") + if type_ in FIELD_TYPES: + if method := FIELD_TYPES[type_].get("method"): + return (method.__get__(cls), type_) # returns the callable classmethod + else: + text = "no method defined" + else: + text = "Unknown Type" + return (f"type:{type_} {text}\n", type_) + + @classmethod + def get_schema_simple_types( + cls, table_name: str, fname: str, fdata: dict[str, Any], type_: str + ) -> tuple[SchemaZoneTexts, str]: + text, subst = cls.get_text_for_simple_types(table_name, fname, fdata, type_) + text["table"] = Helper.FIELD_TEMPLATE.substitute(subst) + if depend_field := fdata.get("sequence_scope"): + text[ + "create_trigger_partitioned_sequences" + ] += cls.get_trigger_generate_partitioned_sequence( + table_name, fname, depend_field + ) + text[ + "table" + ] += f" CONSTRAINT unique_{table_name}_{fname} UNIQUE ({fname}, {depend_field}),\n" + return text, "" + + @classmethod + def get_schema_relation_1_1( + cls, table_name: str, fname: str, fdata: dict[str, Any], type_: str + ) -> tuple[SchemaZoneTexts, str]: + text, subst = cls.get_text_for_simple_types(table_name, fname, fdata, type_) + subst["unique"] = " UNIQUE" + text["table"] = Helper.FIELD_TEMPLATE.substitute(subst) + return text, "" + + @classmethod + def get_text_for_simple_types( + cls, table_name: str, fname: str, fdata: dict[str, Any], type_: str + ) -> tuple[SchemaZoneTexts, SubstDict]: + text = cast(SchemaZoneTexts, defaultdict(str)) + subst, szt = Helper.get_initials(table_name, fname, type_, fdata) + text.update(szt) + if isinstance((tmp := subst["type"]), string.Template): + if maxLength := fdata.get("maxLength"): + tmp = tmp.substitute({"maxLength": maxLength}) + elif isinstance(type_, Decimal): + tmp = tmp.substitute({"maxLength": 6}) + elif isinstance(type_, str): # string + tmp = tmp.substitute({"maxLength": 256}) + subst["type"] = tmp + return text, subst + + @classmethod + def get_schema_color( + cls, table_name: str, fname: str, fdata: dict[str, Any], type_: str + ) -> tuple[SchemaZoneTexts, str]: + text = cast(SchemaZoneTexts, defaultdict(str)) + subst, szt = Helper.get_initials(table_name, fname, type_, fdata) + text.update(szt) + tmpl = FIELD_TYPES[type_]["pg_type"] + subst["type"] = tmpl.substitute({"field_name": fname}) + text["table"] = Helper.FIELD_TEMPLATE.substitute(subst) + return text, "" + + @classmethod + def get_schema_primary_key( + cls, table_name: str, fname: str, fdata: dict[str, Any], type_: str + ) -> tuple[SchemaZoneTexts, str]: + text = cast(SchemaZoneTexts, defaultdict(str)) + subst, tmp = Helper.get_initials(table_name, fname, type_, fdata) + text.update(tmp) + subst["primary_key"] = " PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY" + text["table"] = Helper.FIELD_TEMPLATE.substitute(subst) + return text, "" + + @classmethod + def get_schema_organization_id( + cls, table_name: str, fname: str, fdata: dict[str, Any], type_: str + ) -> tuple[SchemaZoneTexts, str]: + text = cast(SchemaZoneTexts, defaultdict(str)) + subst, tmp = Helper.get_initials(table_name, fname, type_, fdata) + text.update(tmp) + subst["primary_key"] = " GENERATED ALWAYS AS (1) STORED" + text["table"] = Helper.FIELD_TEMPLATE.substitute(subst) + return text, "" + + @classmethod + def get_relation_type( + cls, table_name: str, fname: str, fdata: dict[str, Any], type_: str + ) -> tuple[SchemaZoneTexts, str]: + text = cast(SchemaZoneTexts, defaultdict(str)) + own_table_field = TableFieldType(table_name, fname, fdata) + foreign_table_field: TableFieldType = ( + TableFieldType.get_definitions_from_foreign( + fdata.get("to"), fdata.get("reference") + ) + ) + state, _, final_info, error = InternalHelper.check_relation_definitions( + own_table_field, [foreign_table_field] + ) + + foreign_table = foreign_table_field.table + if state == FieldSqlErrorType.FIELD: + foreign_card, error = InternalHelper.get_cardinality(foreign_table_field) + if foreign_card.startswith("1"): + text, error = cls.get_schema_relation_1_1( + table_name, fname, fdata, "number" + ) + else: + text, error = cls.get_schema_simple_types( + table_name, fname, fdata, "number" + ) + initially_deferred = fdata.get( + "deferred" + ) or ModelsHelper.is_fk_initially_deferred(table_name, foreign_table) + text["alter_table_final"] = ( + Helper.get_foreign_key_table_constraint_as_alter_table( + table_name, + foreign_table, + fname, + foreign_table_field.ref_column, + initially_deferred, + ) + ) + table_name = HelperGetNames.get_table_name(table_name) + text["create_trigger_notify"] = Helper.get_foreign_key_notify_trigger( + table_name, + foreign_table_field.table, + fname, + foreign_table_field.ref_column, + initially_deferred, + ) + elif state == FieldSqlErrorType.SQL: + if sql := fix(fdata.get("sql", "")): + text["view"] = sql + ",\n" + else: + if foreign_table_field.field_def["type"] == "generic-relation": + foreign_column = f"{foreign_table_field.column}_{own_table_field.table}_{own_table_field.ref_column}" + else: + foreign_column = foreign_table_field.column + text["view"] = cls.get_sql_for_relation_1_1( + table_name, + fname, + foreign_table_field.ref_column, + foreign_table, + foreign_column, + ) + if own_table_field.field_def.get("required"): + text["create_trigger_1_1_relation_not_null"] = ( + cls.get_trigger_check_not_null_for_1_1_relation( + own_table_field.table, + own_table_field.column, + foreign_table_field.table, + foreign_column, + ) + ) + if comment := fdata.get("description"): + text["post_view"] += Helper.get_post_view_comment( + HelperGetNames.get_view_name(table_name), fname, comment + ) + text["final_info"] = final_info + return text, error + + @classmethod + def get_sql_for_relation_1_1( + cls, + table_name: str, + fname: str, + ref_column: str, + foreign_table: str, + foreign_column: str, + ) -> str: + table_letter = Helper.get_table_letter(table_name) + letters = [table_letter] + foreign_letter = Helper.get_table_letter(foreign_table, letters) + foreign_table = HelperGetNames.get_table_name(foreign_table) + return f"(select {foreign_letter}.{ref_column} from {foreign_table} {foreign_letter} where {foreign_letter}.{foreign_column} = {table_letter}.{ref_column}) as {fname},\n" + + @classmethod + def get_relation_list_type( + cls, table_name: str, fname: str, fdata: dict[str, Any], type_: str + ) -> tuple[SchemaZoneTexts, str]: + text = cast(SchemaZoneTexts, defaultdict(str)) + own_table_field = TableFieldType(table_name, fname, fdata) + foreign_table_field: TableFieldType = ( + TableFieldType.get_definitions_from_foreign( + fdata.get("to"), + fdata.get("reference"), + ) + ) + state, primary, final_info, error = InternalHelper.check_relation_definitions( + own_table_field, [foreign_table_field] + ) + + if state != FieldSqlErrorType.ERROR: + if primary: + if foreign_table_field.field_def.get("type") == "relation-list": + nm_table_name, value = Helper.get_nm_table_for_n_m_relation_lists( + own_table_field, foreign_table_field + ) + if nm_table_name not in cls.intermediate_tables: + cls.intermediate_tables[nm_table_name] = value + text["create_trigger_notify"] = ( + Helper.get_trigger_for_intermediate_table( + own_table_field, + foreign_table_field, + ) + ) + else: + raise Exception( + f"Tried to create im_table '{nm_table_name}' twice" + ) + if sql := fdata.get("sql", ""): + text["view"] = sql + ",\n" + else: + foreign_table_column = cast(str, foreign_table_field.column) + foreign_table_field_ref_id = cast(str, foreign_table_field.ref_column) + if foreign_table_column or foreign_table_field_ref_id: + if ( + type_ := foreign_table_field.field_def.get("type", "") + ) == "generic-relation": + own_ref_column = own_table_field.ref_column + foreign_table_column += ( + f"_{table_name}_{foreign_table_field.ref_column}" + ) + foreign_table_name = HelperGetNames.get_table_name( + foreign_table_field.table + ) + foreign_table_ref_column = foreign_table_field.ref_column + elif type_ == "relation-list": + if own_table_field.table == foreign_table_field.table: + """Example: committee.forward_to_committee_ids to committee.receive_forwardings_from_committee_ids""" + own_ref_column = own_table_field.ref_column + foreign_table_ref_column = fname[:-1] + foreign_table_name = HelperGetNames.get_nm_table_name( + own_table_field, foreign_table_field + ) + foreign_table_column = ( + foreign_table_field.intermediate_column + ) + else: + own_ref_column = own_table_field.ref_column + foreign_table_ref_column = f"{foreign_table_field.view}_{foreign_table_field.ref_column}" + foreign_table_name = HelperGetNames.get_nm_table_name( + own_table_field, foreign_table_field + ) + foreign_table_column = ( + f"{own_table_field.view}_{own_table_field.ref_column}" + ) + elif type_ == "generic-relation-list": + own_ref_column = own_table_field.ref_column + foreign_table_ref_column = f"{foreign_table_field.view}_{foreign_table_field.ref_column}" + foreign_table_name = HelperGetNames.get_gm_table_name( + foreign_table_field + ) + foreign_table_column = ( + f"{foreign_table_field.intermediate_column}_{table_name}_id" + ) + elif type_ == "relation" or foreign_table_field_ref_id: + own_ref_column = own_table_field.ref_column + foreign_table_ref_column = foreign_table_field.ref_column + foreign_table_name = HelperGetNames.get_table_name( + foreign_table_field.table + ) + foreign_table_column = foreign_table_field.column + else: + raise Exception( + f"Still not implemented for foreign_table type '{type_}' in False case" + ) + text["view"] = cls.get_sql_for_relation_n_1( + table_name, + fname, + own_ref_column, + foreign_table_name, + foreign_table_column, + foreign_table_ref_column, + own_table_field.field_def == foreign_table_field.field_def, + ) + if own_table_field.field_def.get("required"): + if ( + type_ := foreign_table_field.field_def.get("type", "") + ) == "relation": + text["create_trigger_1_n_relation_not_null"] = ( + cls.get_trigger_check_not_null_for_1_n( + own_table_field.table, + own_table_field.column, + foreign_table_field.table, + foreign_table_field.column, + ) + ) + elif type_ == "relation-list": + text["create_trigger_n_m_relation_not_null"] = ( + cls.get_trigger_check_not_null_for_n_m( + own_table_field, foreign_table_field + ) + ) + if ( + own_table_field.table == foreign_table_field.table + and own_table_field.column == foreign_table_field.column + ): + text["create_trigger_unique_ids_pair_code"] = ( + cls.get_trigger_check_unique_ids_pair( + own_table_field.table, + own_table_field.column, + HelperGetNames.get_nm_table_name( + own_table_field, foreign_table_field + ), + ) + ) + if comment := fdata.get("description"): + text["post_view"] += Helper.get_post_view_comment( + HelperGetNames.get_view_name(table_name), fname, comment + ) + text["final_info"] = final_info + return text, error + + @classmethod + def get_sql_for_relation_n_1( + cls, + table_name: str, + fname: str, + own_ref_column: str, + foreign_table_name: str, + foreign_table_column: str, + foreign_table_ref_column: str, + self_reference: bool = False, + ) -> str: + table_letter = Helper.get_table_letter(table_name) + foreign_letter = Helper.get_table_letter(foreign_table_name, [table_letter]) + AGG_TEMPLATE = f"select array_agg({foreign_letter}.{{}} ORDER BY {foreign_letter}.{{}}) from {foreign_table_name} {foreign_letter}" + COND_TEMPLATE = ( + f" where {foreign_letter}.{{}} = {table_letter}.{own_ref_column}" + ) + if not foreign_table_column or not self_reference: + query = AGG_TEMPLATE.format( + foreign_table_ref_column, foreign_table_ref_column + ) + if foreign_table_column: + query += COND_TEMPLATE.format(foreign_table_column) + else: + assert foreign_table_ref_column == ( + col := foreign_table_column + ), f"own {col} and foreign {foreign_table_ref_column} should be equal" + arr1 = AGG_TEMPLATE.format(f"{col}_1", f"{col}_1") + COND_TEMPLATE.format( + f"{col}_2" + ) + arr2 = AGG_TEMPLATE.format(f"{col}_2", f"{col}_2") + COND_TEMPLATE.format( + f"{col}_1" + ) + query = f"select array_cat(({arr1}), ({arr2}))" + return f"({query}) as {fname},\n" + + @classmethod + def get_trigger_generate_partitioned_sequence( + cls, view_name: str, actual_field: str, depend_field: str + ) -> str: + table_name = HelperGetNames.get_table_name(view_name) + return dedent( + f""" + -- definition trigger generate partitioned sequence number for {table_name}.{actual_field} partitioned by {depend_field} + CREATE TRIGGER tr_generate_sequence_{view_name}_{actual_field} BEFORE INSERT ON {table_name} + FOR EACH ROW EXECUTE FUNCTION generate_sequence('{table_name}', '{actual_field}', '{depend_field}'); + """ + ) + + @classmethod + def get_trigger_check_not_null_for_1_1_relation( + cls, + own_collection: str, + own_column: str, + foreign_collection: str, + foreign_column: str, + ) -> str: + own_table_t = HelperGetNames.get_table_name(own_collection) + foreign_table_t = HelperGetNames.get_table_name(foreign_collection) + return dedent( + f""" + -- definition trigger not null for {own_collection}.{own_column} against {foreign_collection}.{foreign_column} + CREATE CONSTRAINT TRIGGER {HelperGetNames.get_not_null_1_1_rel_insert_trigger_name(own_collection, own_column)} AFTER INSERT ON {own_table_t} INITIALLY DEFERRED + FOR EACH ROW EXECUTE FUNCTION check_not_null_for_1_1('{own_collection}', '{own_column}'); + + CREATE CONSTRAINT TRIGGER {HelperGetNames.get_not_null_1_1_rel_upd_del_trigger_name(own_collection, own_column)} AFTER UPDATE OF {foreign_column} OR DELETE ON {foreign_table_t} INITIALLY DEFERRED + FOR EACH ROW EXECUTE FUNCTION check_not_null_for_1_1('{own_collection}', '{own_column}', '{foreign_collection}', '{foreign_column}'); + """ + ) + + @classmethod + def get_trigger_check_not_null_for_1_n( + cls, + own_collection: str, + own_column: str, + foreign_collection: str, + foreign_column: str, + ) -> str: + own_table_t = HelperGetNames.get_table_name(own_collection) + foreign_table_t = HelperGetNames.get_table_name(foreign_collection) + return dedent( + f""" + -- definition trigger not null for {own_collection}.{own_column} against {foreign_collection}.{foreign_column} + CREATE CONSTRAINT TRIGGER {HelperGetNames.get_not_null_rel_list_insert_trigger_name(own_collection, own_column)} AFTER INSERT ON {own_table_t} INITIALLY DEFERRED + FOR EACH ROW EXECUTE FUNCTION check_not_null_for_1_n('{own_table_t}', '{own_column}', '{foreign_table_t}', '{foreign_column}'); + + CREATE CONSTRAINT TRIGGER {HelperGetNames.get_not_null_rel_list_upd_del_trigger_name(own_collection, own_column)} AFTER UPDATE OF {foreign_column} OR DELETE ON {foreign_table_t} INITIALLY DEFERRED + FOR EACH ROW EXECUTE FUNCTION check_not_null_for_1_n('{own_table_t}', '{own_column}', '{foreign_table_t}', '{foreign_column}'); + + """ + ) + + @classmethod + def get_trigger_check_not_null_for_n_m( + cls, own_table_field: TableFieldType, foreign_table_field: TableFieldType + ) -> str: + own_collection = own_table_field.table + own_column = own_table_field.column + own_table = HelperGetNames.get_table_name(own_collection) + foreign_collection = foreign_table_field.table + foreign_column = foreign_table_field.column + intermediate_table_name = HelperGetNames.get_nm_table_name( + own_table_field, foreign_table_field + ) + intermediate_table_own_key = HelperGetNames.get_field_in_n_m_relation_list( + own_table_field, foreign_table_field.table + ) + intermediate_table_foreign_key = HelperGetNames.get_field_in_n_m_relation_list( + foreign_table_field, own_table_field.table + ) + return dedent( + f""" + -- definition trigger not null for {own_collection}.{own_column} against {foreign_collection}.{foreign_column} through {intermediate_table_name} + CREATE CONSTRAINT TRIGGER tr_i_{own_collection}_{own_column} AFTER INSERT ON {own_table} INITIALLY DEFERRED + FOR EACH ROW EXECUTE FUNCTION check_not_null_for_n_m('{intermediate_table_name}', '{own_table}', '{own_column}', '{intermediate_table_own_key}'); + + CREATE CONSTRAINT TRIGGER tr_d_{own_collection}_{own_column} AFTER DELETE ON {intermediate_table_name} INITIALLY DEFERRED + FOR EACH ROW EXECUTE FUNCTION check_not_null_for_n_m('{intermediate_table_name}', '{own_table}', '{own_column}', '{intermediate_table_own_key}', '{intermediate_table_foreign_key}', '{foreign_collection}', '{foreign_column}'); + + """ + ) + + @classmethod + def get_trigger_check_unique_ids_pair( + cls, + view: str, + column: str, + table_name: str, + ) -> str: + base_column_name = column[:-1] + return dedent( + f""" + -- definition trigger unique ids pair for {view}.{column} + CREATE TRIGGER restrict_{view}_{column} BEFORE INSERT OR UPDATE ON {table_name} + FOR EACH ROW EXECUTE FUNCTION check_unique_ids_pair('{base_column_name}'); + + """ + ) + + @classmethod + def get_generic_relation_type( + cls, table_name: str, fname: str, fdata: dict[str, Any], type_: str + ) -> tuple[SchemaZoneTexts, str]: + text = cast(SchemaZoneTexts, defaultdict(str)) + own_table_field = TableFieldType(table_name, fname, fdata) + foreign_table_fields: list[TableFieldType] = ( + InternalHelper.get_definitions_from_foreign_list( + fdata.get("to"), fdata.get("reference") + ) + ) + + state, _, final_info, error = InternalHelper.check_relation_definitions( + own_table_field, foreign_table_fields + ) + + if state == FieldSqlErrorType.FIELD: + text, error = cls.get_schema_simple_types( + table_name, fname, fdata, fdata["type"] + ) + initially_deferred = any( + ModelsHelper.is_fk_initially_deferred( + table_name, foreign_table_field.table + ) + for foreign_table_field in foreign_table_fields + ) + foreign_tables: list[str] = [] + for foreign_table_field in foreign_table_fields: + generic_plain_field_name = f"{own_table_field.column}_{foreign_table_field.table}_{foreign_table_field.ref_column}" + foreign_tables.append(foreign_table_field.table) + text["table"] += Helper.get_generic_combined_fields( + generic_plain_field_name, + own_table_field.column, + foreign_table_field, + ) + text[ + "create_trigger_notify" + ] += Helper.get_trigger_for_generic_relation( + table_name, + generic_plain_field_name, + own_table_field.column, + foreign_table_field.table, + ) + text[ + "alter_table_final" + ] += Helper.get_foreign_key_table_constraint_as_alter_table( + own_table_field.table, + foreign_table_field.table, + generic_plain_field_name, + foreign_table_field.ref_column, + initially_deferred, + ) + text["table"] += Helper.get_generic_field_constraint( + own_table_field.column, foreign_tables + ) + text["final_info"] = final_info + return text, error + + @classmethod + def get_generic_relation_list_type( + cls, table_name: str, fname: str, fdata: dict[str, Any], type_: str + ) -> tuple[SchemaZoneTexts, str]: + text = cast(SchemaZoneTexts, defaultdict(str)) + own_table_field = TableFieldType(table_name, fname, fdata) + foreign_table_fields: list[TableFieldType] = ( + InternalHelper.get_definitions_from_foreign_list( + fdata.get("to"), fdata.get("reference") + ) + ) + state, primary, final_info, error = InternalHelper.check_relation_definitions( + own_table_field, foreign_table_fields + ) + + if state == FieldSqlErrorType.SQL and primary: + # create gm-intermediate table + if primary: + gm_foreign_table, value = Helper.get_gm_table_for_gm_nm_relation_lists( + own_table_field, foreign_table_fields + ) + text[ + "create_trigger_notify" + ] += Helper.get_trigger_for_generic_intermediate_table( + own_table_field, foreign_table_fields + ) + if gm_foreign_table not in cls.intermediate_tables: + cls.intermediate_tables[gm_foreign_table] = value + else: + raise Exception( + f"Tried to create gm_table '{gm_foreign_table}' twice" + ) + + # add field to view definition of table_name + text["view"] = cls.get_sql_for_relation_n_1( + table_name, + fname, + own_table_field.ref_column, + gm_foreign_table, + f"{own_table_field.table}_{own_table_field.ref_column}", + own_table_field.intermediate_column, + ) + if comment := fdata.get("description"): + text["post_view"] += Helper.get_post_view_comment( + HelperGetNames.get_view_name(table_name), fname, comment + ) + + text["final_info"] = final_info + return text, error + + +class Helper: + FILE_TEMPLATE_HEADER = dedent( + """ + -- schema_relational.sql for initial database setup OpenSlides + -- Code generated. DO NOT EDIT. + """ + ) + FILE_TEMPLATE_CONSTANT_DEFINITIONS = dedent( + """ + CREATE EXTENSION hstore; -- included in standard postgres-installations, check for alpine + + CREATE FUNCTION generate_sequence() + RETURNS trigger + AS $sequences_trigger$ + -- Creates a sequence for the id given by depend_field NEW data if it doesn't exist. + -- Writes the next value to for this sequence to NEW. + -- In case a number is given in actual_column of the NEW record that is used + -- and the corresponding sequence increased if necessary. + -- Usage with 3 parameters IN TRIGGER DEFINITION: + -- table_name: table this is treated for + -- actual_column: column that will be filled with the actual value + -- depend_field: field that differentiates the sequences. usually meeting_id + DECLARE + table_name TEXT := TG_ARGV[0]; + actual_column TEXT := TG_ARGV[1]; + depend_field TEXT := TG_ARGV[2]; + depend_field_id INTEGER; + sequence_name TEXT; + sequence_value INTEGER; + sequence_max INTEGER; + BEGIN + depend_field_id := hstore(NEW) -> (depend_field); + sequence_name := table_name || '_' || depend_field || depend_field_id || '_' || actual_column || '_seq'; + EXECUTE format('CREATE SEQUENCE IF NOT EXISTS %I OWNED BY %I.%I', sequence_name, table_name, actual_column); + sequence_value := hstore(NEW) -> actual_column; + IF sequence_value IS NULL THEN + sequence_value := nextval(sequence_name); + ELSE + EXECUTE format('SELECT last_value FROM %I', sequence_name) INTO sequence_max; + -- <= because the unused sequence starts with last_value=1 and is_called=f and needs to be written to. + IF sequence_max <= sequence_value THEN + SELECT setval(sequence_name, sequence_value) INTO sequence_value; + END IF; + END IF; + RETURN populate_record(NEW, format('%s=>%s',actual_column, sequence_value)::hstore); + END; + $sequences_trigger$ + LANGUAGE plpgsql; + + CREATE FUNCTION log_modified_models() RETURNS trigger AS $log_modified_trigger$ + DECLARE + escaped_table_name varchar; + operation_var TEXT; + fqid_var TEXT; + BEGIN + escaped_table_name := TG_ARGV[0]; + operation_var := LOWER(TG_OP); + fqid_var := escaped_table_name || '/' || NEW.id; + IF (TG_OP = 'DELETE') THEN + fqid_var := escaped_table_name || '/' || OLD.id; + END IF; + + INSERT INTO os_notify_log_t (operation, fqid, xact_id, timestamp) + VALUES (operation_var, fqid_var, pg_current_xact_id(), 'now') + ON CONFLICT (operation,fqid,xact_id) DO NOTHING; + RETURN NULL; -- AFTER TRIGGER needs no return + END; + $log_modified_trigger$ LANGUAGE plpgsql; + + CREATE FUNCTION check_unique_ids_pair() + RETURNS trigger + AS $unique_ids_pair_trigger$ + -- usage with 1 parameter IN TRIGGER DEFINITION: + -- base_column_name: name of write fields before adding numeric suffixes + -- Guards against mirrored duplicates by skipping one of the pairs. + DECLARE + base_column_name text; + value_1 integer; + value_2 integer; + BEGIN + base_column_name := TG_ARGV[0]; + value_1 := hstore(NEW) -> (base_column_name || '_1'); + value_2 := hstore(NEW) -> (base_column_name || '_2'); + + IF (value_1 > value_2) THEN + RETURN NULL; + END IF; + + RETURN NEW; + END; + $unique_ids_pair_trigger$ + LANGUAGE plpgsql; + + CREATE FUNCTION notify_transaction_end() RETURNS trigger AS $notify_trigger$ + DECLARE + payload TEXT; + body_content_text TEXT; + BEGIN + -- Running the trigger for the first time in a transaction creates the table and after committing the transaction the table is dropped. + -- Every next run of the trigger in this transaction raises a notice that the table exists. Setting the log_min_messages to notice increases the noise because of such messages. + CREATE LOCAL TEMPORARY TABLE + IF NOT EXISTS tbl_notify_counter_tx_once ( + "id" integer NOT NULL PRIMARY KEY GENERATED ALWAYS AS IDENTITY + ) ON COMMIT DROP; + + -- If running for the first time, the transaction id is send via os_notify. + IF NOT EXISTS (SELECT * FROM tbl_notify_counter_tx_once) THEN + INSERT INTO tbl_notify_counter_tx_once DEFAULT VALUES; + payload := '{"xactId":' || + pg_current_xact_id() || + '}'; + PERFORM pg_notify('os_notify', payload); + END IF; + + RETURN NULL; -- AFTER TRIGGER needs no return + END; + $notify_trigger$ LANGUAGE plpgsql; + + CREATE OR REPLACE FUNCTION log_modified_related_models() + RETURNS trigger AS $log_modified_related_trigger$ + DECLARE + fqid_var TEXT; + ref_column TEXT; + foreign_table TEXT; + foreign_id TEXT; + i INTEGER := 0; + BEGIN + + WHILE i < TG_NARGS LOOP + foreign_table := TG_ARGV[i]; + ref_column := TG_ARGV[i+1]; + + IF (TG_OP = 'DELETE') THEN + EXECUTE format('SELECT ($1).%I', ref_column) INTO foreign_id USING OLD; + ELSE + EXECUTE format('SELECT ($1).%I', ref_column) INTO foreign_id USING NEW; + END IF; + + IF foreign_id IS NOT NULL THEN + fqid_var := foreign_table || '/' || foreign_id; + INSERT INTO os_notify_log_t (operation, fqid, xact_id, timestamp) + VALUES ('update', fqid_var, pg_current_xact_id(), now()) + ON CONFLICT (operation,fqid,xact_id) DO NOTHING; + END IF; + + i := i + 2; + END LOOP; + + RETURN NULL; -- AFTER TRIGGER needs no return + END; + $log_modified_related_trigger$ LANGUAGE plpgsql; + + CREATE TABLE os_notify_log_t ( + id integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + operation varchar(32), + fqid varchar(256) NOT NULL, + xact_id xid8, + timestamp timestamptz, + CONSTRAINT unique_fqid_xact_id_operation UNIQUE (operation,fqid,xact_id) + ); + + CREATE TABLE version ( + migration_index INTEGER PRIMARY KEY, + migration_state TEXT, + replace_tables JSONB + ); + + CREATE OR REPLACE FUNCTION prevent_writes() RETURNS trigger AS $read_only_trigger$ + BEGIN + RAISE EXCEPTION 'Table % is currently read-only.', TG_TABLE_NAME; + END; + $read_only_trigger$ LANGUAGE plpgsql; + """ + ) + NOT_NULL_TRIGGER_FUNCTION_TEMPLATE = string.Template( + dedent( + """ + CREATE FUNCTION check_not_null_for_${trigger_type}() RETURNS trigger AS $$not_null_trigger$$ + ${docstring} + DECLARE + ${parameters_declaration} + BEGIN + IF (TG_OP = 'INSERT') THEN + -- in case of INSERT the view is checked on itself so the own id is applicable + own_id := NEW.id; + ELSE + own_id := hstore(OLD) -> ${foreign_column}; + EXECUTE format('SELECT 1 FROM %I WHERE "id" = %L', ${query_relation}, own_id) INTO counted; + IF (counted IS NULL) THEN + -- if the earlier referenced row was deleted (in the same transaction) we can quit. + RETURN NULL; + END IF; + END IF; + + ${select_expression} + IF (counted is NULL) THEN${own_collection_definition} + error_message := format('Trigger %s: NOT NULL CONSTRAINT VIOLATED for %s/%s/%s', TG_NAME, own_collection, own_id, own_column); + IF ${ud_operations_filter} THEN${foreign_collection_definition} + foreign_id := ${foreign_id}; + error_message := error_message || format(' from relationship before %s/%s/%s', foreign_collection, foreign_id, foreign_column); + END IF; + RAISE EXCEPTION '%', error_message; + END IF; + RETURN NULL; -- AFTER TRIGGER needs no return + END; + $$not_null_trigger$$ language plpgsql; + """ + ) + ) + COLLECTION_FROM_TABLE_TEMPLATE = string.Template( + "${parameter} := SUBSTRING(${table_t} FOR LENGTH(${table_t}) - 2);" + ) + FIELD_TEMPLATE = string.Template( + " ${field_name} ${type}${primary_key}${required}${unique}${check_enum}${minimum}${minLength}${default},\n" + ) + INTERMEDIATE_TABLE_N_M_RELATION_TEMPLATE = string.Template( + dedent( + """ + CREATE TABLE ${table_name} ( + ${field1} integer NOT NULL REFERENCES ${table1} (id) ON DELETE CASCADE INITIALLY DEFERRED, + ${field2} integer NOT NULL REFERENCES ${table2} (id) ON DELETE CASCADE INITIALLY DEFERRED, + PRIMARY KEY (${list_of_keys}) + ); + CREATE INDEX ON ${table_name} (${field1}); + CREATE INDEX ON ${table_name} (${field2}); + """ + ) + ) + INTERMEDIATE_TABLE_G_M_RELATION_TEMPLATE = string.Template( + dedent( + """ + CREATE TABLE ${table_name} ( + ${own_table_name_with_ref_column} integer NOT NULL REFERENCES ${own_table_name}(${own_table_ref_column}) ON DELETE CASCADE INITIALLY DEFERRED, + ${own_table_column} varchar(100) NOT NULL, + ${foreign_table_ref_lines} + CONSTRAINT ${valid_constraint_name} CHECK (split_part(${own_table_column}, '/', 1) IN ${tuple_of_foreign_table_names}), + CONSTRAINT ${unique_constraint_name} UNIQUE (${own_table_name_with_ref_column}, ${own_table_column}) + ); + CREATE INDEX ON ${table_name} (${own_table_name_with_ref_column}); + CREATE INDEX ON ${table_name} (${own_table_column}); + """ + ) + ) + GM_FOREIGN_TABLE_LINE_TEMPLATE = string.Template( + " ${gm_content_field} integer GENERATED ALWAYS AS (CASE WHEN split_part(${own_table_column}, '/', 1) = '${foreign_view_name}' THEN cast(split_part(${own_table_column}, '/', 2) AS INTEGER) ELSE null END) STORED REFERENCES ${foreign_table_name}(id) ON DELETE CASCADE INITIALLY DEFERRED," + ) + + RELATION_LIST_AGENDA = dedent( + """ + /* Relation-list infos + Generated: What will be generated for left field + FIELD: a usual Database field + SQL: a sql-expression in a view + ***: Error + Field Attributes:Field Attributes opposite side + 1: cardinality 1 + 1G: cardinality 1 with generic-relation field + n: cardinality n + nG: cardinality n with generic-relation-list field + t: "to" defined + r: "reference" defined + s: sql directive inclusive sql-statement + R: Required + Model.Field -> Model.Field + model.field names + */ + + """ + ) + + @staticmethod + def get_table_letter(table_name: str, letters: list[str] = []) -> str: + letter = HelperGetNames.get_table_name(table_name)[0] + count = -1 + start_letter = letter + while True: + if letter in letters: + count += 1 + if count == 0: + start_letter = "".join([part[0] for part in table_name.split("_")])[ + :2 + ] + letter = start_letter + else: + letter = start_letter + str(count) + else: + return letter + + @staticmethod + def get_table_head(table_name: str) -> str: + return f"\nCREATE TABLE {HelperGetNames.get_table_name(table_name)} (\n" + + @staticmethod + def get_table_body_end(code: str) -> str: + code = code[:-2] + "\n" # last attribute line without ",", but with "\n" + code += ");\n\n" + return code + + @staticmethod + def get_view_head(table_name: str) -> str: + return f"\nCREATE VIEW {HelperGetNames.get_view_name(table_name)} AS SELECT *" + + @staticmethod + def get_view_body_end(table_name: str, code: str) -> str: + # change the code only if there is + if code: + # comma and "\n" for the header + # last attribute line without ",", but with "\n" + code = ",\n" + code[:-2] + "\n" + else: + code = " " + code += f"FROM {HelperGetNames.get_table_name(table_name)} {Helper.get_table_letter(table_name)};\n\n" + return code + + @staticmethod + def get_notify_trigger(table_name: str) -> str: + trigger_name = HelperGetNames.get_notify_trigger_name(table_name) + own_table = HelperGetNames.get_table_name(table_name) + escaped_table_name = "'" + table_name + "'" + code = f"CREATE TRIGGER {trigger_name} AFTER INSERT OR UPDATE OR DELETE ON {own_table}\n" + code += f"FOR EACH ROW EXECUTE FUNCTION log_modified_models({escaped_table_name});\n" + code += f"CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON {own_table}\n" + code += "DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end();\n" + return code + + @staticmethod + def get_alter_table_final_code(code: str) -> str: + return f"-- Alter table final relation commands\n{code}\n\n" + + @staticmethod + def get_undecided_all(table_name: str, code: str) -> str: + return ( + f"/*\n Fields without SQL definition for table {table_name}\n\n{code}\n*/\n" + ) + + @staticmethod + def get_check_enum( + table_name: str, fname: str, enum_: list[Any], type_: str + ) -> str: + check_enum_constraint_name = HelperGetNames.get_check_enum_constraint_name( + table_name, fname + ) + if type_.startswith("number"): + enumeration = ", ".join([str(item) for item in enum_]) + elif type_.startswith("string"): + enumeration = ", ".join([f"'{item}'" for item in enum_]) + else: + raise Exception(f"enum for type {type_} not implemented") + if type_.endswith("[]"): + condition = f"{fname} <@ ARRAY[{enumeration}]::varchar[]" + else: + condition = f"{fname} IN ({enumeration})" + return f" CONSTRAINT {check_enum_constraint_name} CHECK ({condition})" + + @staticmethod + def get_foreign_key_table_constraint_as_alter_table( + table_name: str, + foreign_table: str, + own_column: str, + fk_column: str, + initially_deferred: bool = False, + delete_action: str = "", + update_action: str = "", + ) -> str: + FOREIGN_KEY_TABLE_CONSTRAINT_TEMPLATE = string.Template( + "ALTER TABLE ${own_table} ADD FOREIGN KEY(${own_column}) REFERENCES ${foreign_table}(${fk_column})${initially_deferred}${delete_action}${update_action};\n" + "CREATE INDEX ON ${own_table} (${own_column});\n" + ) + + if initially_deferred: + text_initially_deferred = " INITIALLY DEFERRED" + else: + text_initially_deferred = "" + own_table = HelperGetNames.get_table_name(table_name) + foreign_table = HelperGetNames.get_table_name(foreign_table) + result = FOREIGN_KEY_TABLE_CONSTRAINT_TEMPLATE.substitute( + { + "own_table": own_table, + "foreign_table": foreign_table, + "own_column": own_column, + "fk_column": fk_column, + "initially_deferred": text_initially_deferred, + "delete_action": Helper.get_on_action_mode(delete_action, True), + "update_action": Helper.get_on_action_mode(update_action, False), + } + ) + return result + + @staticmethod + def get_on_action_mode(action: str, delete: bool) -> str: + if action: + if (actionUpper := action.upper()) in SQL_Delete_Update_Options: + return f" ON {'DELETE' if delete else 'UPDATE'} {SQL_Delete_Update_Options(actionUpper)}" + else: + raise Exception(f"{action} is not a valid action mode") + return "" + + @staticmethod + def get_foreign_key_notify_trigger( + table_name: str, + foreign_table: str, + ref_column: str, + fk_columns: list[str] | str, + initially_deferred: bool = False, + delete_action: str = "", + update_action: str = "", + ) -> str: + trigger_name = HelperGetNames.get_notify_related_trigger_name( + table_name, ref_column + ) + own_table = HelperGetNames.get_table_name(table_name) + return f"""CREATE TRIGGER {trigger_name} AFTER INSERT OR UPDATE OF {ref_column} OR DELETE ON {own_table} +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('{foreign_table}', '{ref_column}');\n""" + + @staticmethod + def get_nm_table_for_n_m_relation_lists( + own_table_field: TableFieldType, foreign_table_field: TableFieldType + ) -> tuple[str, str]: + nm_table_name = HelperGetNames.get_nm_table_name( + own_table_field, foreign_table_field + ) + field1 = HelperGetNames.get_field_in_n_m_relation_list( + own_table_field, foreign_table_field.table + ) + field2 = HelperGetNames.get_field_in_n_m_relation_list( + foreign_table_field, own_table_field.table + ) + if field1 == field2: + field1 += "_1" + field2 += "_2" + table_name = HelperGetNames.get_table_name(nm_table_name) + text = Helper.INTERMEDIATE_TABLE_N_M_RELATION_TEMPLATE.substitute( + { + "table_name": table_name, + "field1": field1, + "table1": HelperGetNames.get_table_name(own_table_field.table), + "field2": field2, + "table2": HelperGetNames.get_table_name(foreign_table_field.table), + "list_of_keys": ", ".join([field1, field2]), + } + ) + return nm_table_name, text + + @staticmethod + def get_gm_table_for_gm_nm_relation_lists( + own_table_field: TableFieldType, foreign_table_fields: list[TableFieldType] + ) -> tuple[str, str]: + gm_table_name = HelperGetNames.get_gm_table_name(own_table_field) + joined_table_names = ( + "('" + + "', '".join( + [ + foreign_table_field.table + for foreign_table_field in foreign_table_fields + ] + ) + + "')" + ) + foreign_table_ref_lines = [] + own_table_column = own_table_field.intermediate_column + for foreign_table_field in foreign_table_fields: + foreign_table_name = foreign_table_field.table + subst_dict = { + "own_table_column": own_table_column, + "foreign_table_name": HelperGetNames.get_table_name(foreign_table_name), + "foreign_view_name": foreign_table_name, + "gm_content_field": HelperGetNames.get_gm_content_field( + own_table_column, foreign_table_name + ), + } + foreign_table_ref_lines.append( + Helper.GM_FOREIGN_TABLE_LINE_TEMPLATE.substitute(subst_dict) + ) + + text = Helper.INTERMEDIATE_TABLE_G_M_RELATION_TEMPLATE.substitute( + { + "table_name": gm_table_name, + "own_table_name": HelperGetNames.get_table_name(own_table_field.table), + "own_table_name_with_ref_column": ( + own_table_name_with_ref_column := f"{own_table_field.table}_{own_table_field.ref_column}" + ), + "own_table_ref_column": own_table_field.ref_column, + "own_table_column": own_table_column, + "tuple_of_foreign_table_names": joined_table_names, + "foreign_table_ref_lines": "\n".join(foreign_table_ref_lines), + "valid_constraint_name": HelperGetNames.get_generic_valid_constraint_name( + own_table_column + ), + "unique_constraint_name": HelperGetNames.get_generic_unique_constraint_name( + own_table_name_with_ref_column, own_table_column + ), + } + ) + return gm_table_name, text + + @staticmethod + def get_trigger_for_intermediate_table( + own_table_field: TableFieldType, foreign_table_field: TableFieldType + ) -> str: + + field1 = HelperGetNames.get_field_in_n_m_relation_list( + own_table_field, foreign_table_field.table + ) + field2 = HelperGetNames.get_field_in_n_m_relation_list( + foreign_table_field, own_table_field.table + ) + if field1 == field2: + field1 += "_1" + field2 += "_2" + nm_table_name = HelperGetNames.get_nm_table_name( + own_table_field, foreign_table_field + ) + + table_name = HelperGetNames.get_table_name(nm_table_name) + trigger_name = f"tr_log_{table_name}" + + return f""" +CREATE TRIGGER {trigger_name} AFTER INSERT OR UPDATE OR DELETE ON {nm_table_name} +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('{own_table_field.table}','{field1}','{foreign_table_field.table}','{field2}'); +CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON {nm_table_name} +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); +""" + + @staticmethod + def get_trigger_for_generic_relation( + table_name: str, + generic_plain_field_name: str, + own_column: str, + foreign_table: str, + ) -> str: + trigger_name = f"tr_log_{foreign_table}_{generic_plain_field_name}" + own_table_name = HelperGetNames.get_table_name(table_name) + return f""" +CREATE TRIGGER {trigger_name} AFTER INSERT OR UPDATE OF {generic_plain_field_name} OR DELETE ON {own_table_name} +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('{foreign_table}','{generic_plain_field_name}'); +""" + + @staticmethod + def get_trigger_for_generic_intermediate_table( + own_table_field: TableFieldType, foreign_table_fields: list[TableFieldType] + ) -> str: + + gm_table_name = HelperGetNames.get_gm_table_name(own_table_field) + trigger_text = "" + + for foreign_table_field in foreign_table_fields: + gm_content_field = HelperGetNames.get_gm_content_field( + own_table_field.intermediate_column, foreign_table_field.table + ) + trigger_name = f"tr_log_{gm_content_field}_{gm_table_name}" + own_table_name_with_ref_column = ( + f"{own_table_field.table}_{own_table_field.ref_column}" + ) + trigger_text += f""" +CREATE TRIGGER {trigger_name} AFTER INSERT OR UPDATE OF {gm_content_field} OR DELETE ON {gm_table_name} +FOR EACH ROW EXECUTE FUNCTION log_modified_related_models('{own_table_field.table}','{own_table_name_with_ref_column}','{foreign_table_field.table}','{gm_content_field}'); +""" + trigger_text += f"""CREATE CONSTRAINT TRIGGER notify_transaction_end AFTER INSERT OR UPDATE OR DELETE ON {gm_table_name} +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION notify_transaction_end(); +""" + return trigger_text + + @staticmethod + def get_initials( + table_name: str, fname: str, type_: str, fdata: dict[str, Any] + ) -> tuple[SubstDict, SchemaZoneTexts]: + text = cast(SchemaZoneTexts, defaultdict(str)) + flist: list[str] = [ + cast(str, form[1]) + for form in Formatter().parse(Helper.FIELD_TEMPLATE.template) + ] + subst: SubstDict = cast(SubstDict, {k: "" for k in flist}) + subst_type = FIELD_TYPES[type_]["pg_type"] + subst.update({"field_name": fname, "type": subst_type}) + if fdata.get("required"): + subst["required"] = " NOT NULL" + if (default := fdata.get("default")) is not None: + if isinstance(default, str) or type_ in ("string", "text"): + subst["default"] = f" DEFAULT '{default}'" + elif isinstance(default, (int, bool, float)): + subst["default"] = f" DEFAULT {default}" + elif isinstance(default, list): + tmp = '{"' + '", "'.join(default) + '"}' if default else "{}" + subst["default"] = f" DEFAULT '{tmp}'" + else: + raise Exception( + f"{table_name}.{fname}: seems to be an invalid default value" + ) + if (enum_ := fdata.get("enum")) or ( + enum_ := fdata.get("items", {}).get("enum") + ): + subst["check_enum"] = Helper.get_check_enum(table_name, fname, enum_, type_) + if (minimum := fdata.get("minimum")) is not None: + minimum_constraint_name = HelperGetNames.get_minimum_constraint_name(fname) + subst["minimum"] = ( + f" CONSTRAINT {minimum_constraint_name} CHECK ({fname} >= {minimum})" + ) + if minLength := fdata.get("minLength"): + minlength_constraint_name = HelperGetNames.get_minlength_constraint_name( + fname + ) + subst["minLength"] = ( + f" CONSTRAINT {minlength_constraint_name} CHECK (char_length({fname}) >= {minLength})" + ) + if comment := fdata.get("description"): + text["alter_table"] = Helper.get_post_view_comment( + HelperGetNames.get_table_name(table_name), fname, comment + ) + return subst, text + + @staticmethod + def get_post_view_comment(entity_name: str, fname: str, comment: str) -> str: + comment = comment.replace("'", "''") + return f"comment on column {entity_name}.{fname} is '{comment}';\n" + + @staticmethod + def get_generic_combined_fields( + generic_plain_field_name: str, own_column: str, foreign_field: TableFieldType + ) -> str: + foreign_table = foreign_field.table + foreign_card, error = InternalHelper.get_cardinality(foreign_field) + if error: + raise Exception(error) + if foreign_card.startswith("1"): + unique = " UNIQUE" + else: + unique = "" + return f" {generic_plain_field_name} integer{unique} GENERATED ALWAYS AS (CASE WHEN split_part({own_column}, '/', 1) = '{foreign_table}' THEN cast(split_part({own_column}, '/', 2) AS INTEGER) ELSE null END) STORED,\n" + + @staticmethod + def get_generic_field_constraint(own_column: str, foreign_tables: list[str]) -> str: + constraint_name = HelperGetNames.get_generic_valid_constraint_name(own_column) + return f""" CONSTRAINT {constraint_name} CHECK (split_part({own_column}, '/', 1) IN ('{"','".join(foreign_tables)}')),\n""" + + @staticmethod + def prefix_error(method_or_str: str, table_name: str, fname: str) -> str: + return f" {table_name}/{fname}: {method_or_str}" + + +class ModelsHelper: + @staticmethod + def is_fk_initially_deferred(own_table: str, foreign_table: str) -> bool: + """ + The "Initially deferred" in fk-definition is necessary, + if 2 related tables require both the relation to the other table + """ + + def _first_to_second(t1: str, t2: str) -> bool: + for field in InternalHelper.MODELS[t1].values(): + if field.get("required") and field["type"].startswith("relation"): + ftable = ModelsHelper.get_foreign_table_from_to_or_reference( + field.get("to"), field.get("reference") + ) + if ftable == t2: + return True + return False + + return True + # TODO: Will be reverted in a future issue + # if _first_to_second(own_table, foreign_table): + # return _first_to_second(foreign_table, own_table) + # return False + + @staticmethod + def get_foreign_table_from_to_or_reference( + to: str | None, reference: str | None + ) -> str: + if reference: + result = InternalHelper.ref_compiled.search(reference) + if result is None: + return reference.strip() + re_groups = result.groups() + return re_groups[0] + elif to: + return to.split(KEYSEPARATOR)[0] + else: + raise Exception("Relation field without reference or to") + + +FIELD_TYPES: dict[str, dict[str, Any]] = { + "string": { + "pg_type": string.Template("varchar(${maxLength})"), + "method": GenerateCodeBlocks.get_schema_simple_types, + }, + "number": { + "pg_type": "integer", + "method": GenerateCodeBlocks.get_schema_simple_types, + }, + "boolean": { + "pg_type": "boolean", + "method": GenerateCodeBlocks.get_schema_simple_types, + }, + "JSON": {"pg_type": "jsonb", "method": GenerateCodeBlocks.get_schema_simple_types}, + "HTMLStrict": { + "pg_type": "text", + "method": GenerateCodeBlocks.get_schema_simple_types, + }, + "HTMLPermissive": { + "pg_type": "text", + "method": GenerateCodeBlocks.get_schema_simple_types, + }, + "float": { + "pg_type": "double precision", + "method": GenerateCodeBlocks.get_schema_simple_types, + }, + "decimal(6)": { + "pg_type": "decimal(16,6)", + "method": GenerateCodeBlocks.get_schema_simple_types, + }, + "timestamp": { + "pg_type": "timestamptz", + "method": GenerateCodeBlocks.get_schema_simple_types, + }, + "color": { + "pg_type": string.Template( + "varchar(7) CHECK (${field_name} is null or ${field_name} ~* '^#[a-f0-9]{6}$$')" + ), + "method": GenerateCodeBlocks.get_schema_color, + }, + "string[]": { + "pg_type": string.Template("varchar(${maxLength})[]"), + "method": GenerateCodeBlocks.get_schema_simple_types, + }, + "number[]": { + "pg_type": "integer[]", + "method": GenerateCodeBlocks.get_schema_simple_types, + }, + "text[]": { + "pg_type": "text[]", + "method": GenerateCodeBlocks.get_schema_simple_types, + }, + "text": {"pg_type": "text", "method": GenerateCodeBlocks.get_schema_simple_types}, + "relation": {"pg_type": "integer", "method": GenerateCodeBlocks.get_relation_type}, + "relation-list": { + "pg_type": "integer[]", + "method": GenerateCodeBlocks.get_relation_list_type, + }, + "generic-relation": { + "pg_type": "varchar(100)", + "method": GenerateCodeBlocks.get_generic_relation_type, + }, + "generic-relation-list": { + "pg_type": "varchar(100)[]", + "method": GenerateCodeBlocks.get_generic_relation_list_type, + }, + # special defined + "primary_key": { + "pg_type": "integer", + "method": GenerateCodeBlocks.get_schema_primary_key, + }, + "organization_id": { + "pg_type": "integer", + "method": GenerateCodeBlocks.get_schema_organization_id, + }, +} + + +def main() -> None: + """ + Main entry point for this script to generate the schema_relational.sql from models.yml. + """ + + # Retrieve models.yml from call-parameter for testing purposes, local file or GitHub + if len(sys.argv) > 1: + file = sys.argv[1] + else: + file = str(SOURCE) + + _, checksum = InternalHelper.read_models_yml(file) + + ( + pre_code, + table_name_code, + view_name_code, + alter_table_code, + final_info_code, + missing_handled_attributes, + im_table_code, + create_trigger_partitioned_sequences_code, + create_trigger_1_1_relation_not_null_code, + create_trigger_1_n_relation_not_null_code, + create_trigger_n_m_relation_not_null_code, + create_trigger_unique_ids_pair_code, + create_trigger_notify_code, + errors, + ) = GenerateCodeBlocks.generate_the_code() + with open(DESTINATION, "w") as dest: + dest.write(Helper.FILE_TEMPLATE_HEADER) + dest.write("-- MODELS_YML_CHECKSUM = " + repr(checksum) + "\n") + dest.write("\n\n-- Function and meta table definitions\n") + dest.write(Helper.FILE_TEMPLATE_CONSTANT_DEFINITIONS) + dest.write(pre_code) + dest.write("\n\n-- Table definitions\n") + dest.write(table_name_code) + dest.write("\n\n-- Intermediate table definitions\n") + dest.write(im_table_code) + dest.write("\n\n-- View definitions\n") + dest.write(view_name_code) + dest.write("\n\n-- Alter table relations\n") + dest.write(alter_table_code) + dest.write("\n\n-- Create triggers generating partitioned sequences\n") + dest.write(create_trigger_partitioned_sequences_code) + dest.write( + "\n\n-- Create triggers checking foreign_id not null for view-relations and no duplicates in 1:1 relationships\n" + ) + dest.write(create_trigger_1_1_relation_not_null_code) + dest.write( + "\n\n-- Create triggers checking foreign_id not null for 1:n relationships\n" + ) + dest.write(create_trigger_1_n_relation_not_null_code) + dest.write( + "\n\n-- Create triggers checking foreign_ids not null for n:m relationships\n" + ) + dest.write(create_trigger_n_m_relation_not_null_code) + dest.write( + "\n\n-- Create triggers preventing mirrored duplicates in fields referencing themselves\n" + ) + dest.write(create_trigger_unique_ids_pair_code) + dest.write("\n\n-- Create triggers for notify\n") + dest.write(create_trigger_notify_code) + dest.write(Helper.RELATION_LIST_AGENDA) + dest.write("/*\n") + dest.write(final_info_code) + dest.write("*/\n") + if errors: + dest.write(f"/*\nThere are {len(errors)} errors/warnings\n") + dest.write("".join(errors)) + dest.write("*/\n") + dest.write( + f"\n/* Missing attribute handling for {', '.join(missing_handled_attributes)} */" + ) + if errors: + print(f"Models file {DESTINATION} created with {len(errors)} errors/warnings\n") + print("".join(errors)) + else: + print(f"Models file {DESTINATION} successfully created.") + + +if __name__ == "__main__": + main() diff --git a/dev/src/helper_get_names.py b/dev/src/helper_get_names.py new file mode 100644 index 00000000..bbc1e9b0 --- /dev/null +++ b/dev/src/helper_get_names.py @@ -0,0 +1,599 @@ +import hashlib +import os +import re +from collections.abc import Callable +from enum import Enum +from typing import Any, TypedDict, cast + +import requests +import yaml + +KEYSEPARATOR = "/" + + +class TableFieldType: + def __init__( + self, + table: str, + column: str, + field_def: dict[str, Any] | None, + ref_column: str = "id", + ): + self.table = table + self.view = table.rstrip("_t") + self.column = column + self.intermediate_column = column[:-1] + self.field_def: dict[str, Any] = field_def or {} + self.ref_column = ref_column + + @property + def collectionfield(self) -> str: + if self.table: + return f"{self.table}{KEYSEPARATOR}{self.column}" + else: + return "-" + + @staticmethod + def get_definitions_from_foreign( + to: str | None, reference: str | None + ) -> "TableFieldType": + tname = "" + fname = "" + tfield: dict[str, Any] = {} + ref_column = "" + if to: + tname, fname, tfield = InternalHelper.get_field_definition_from_to(to) + ref_column = "id" + if reference: + tname, ref_column = InternalHelper.get_foreign_key_table_column(reference) + + return TableFieldType(tname, fname, tfield, ref_column) + + +class ToDict(TypedDict): + """Defines the dict keys for the to-Attribute of generic relations in field definitions""" + + collections: list[str] + field: str + + +class FieldSqlErrorType(Enum): + FIELD = 1 + SQL = 2 + ERROR = 3 + + +class HelperGetNames: + MAX_LEN = 63 + trigger_unique_list: list[str] = [] + + @staticmethod + def max_length(func: Callable) -> Callable: + def wrapper(*args, **kwargs) -> str: # type:ignore + name = func(*args, **kwargs) + assert ( + len(name) <= HelperGetNames.MAX_LEN + ), f"Name '{name}' generated too long in function {func}!" + return name + + return wrapper + + @staticmethod + def get_initial_letters(words: str) -> str: + """get only the initial letters of words, separated by _, + if the given string has more than 25 letters + """ + if len(words) > 25: + return "".join([word[0] for word in words.split("_")]) + else: + return words + + @staticmethod + @max_length + def get_table_name(table_name: str, migration: bool = False) -> str: + """ + Gets the table name as old collection name with suffix '_t'. + If migration is True '_m'. + Takes either collection or table name. + """ + if migration: + if table_name.endswith("_t"): + table_name = table_name[:-2] + return table_name + "_m" + elif not table_name.endswith("_t"): + return table_name + "_t" + else: + return table_name + + @staticmethod + @max_length + def get_view_name(table_name: str) -> str: + """gets the name of a view. Its the collection name in quotes""" + return f'"{table_name}"' + + @staticmethod + @max_length + def get_nm_table_name(own: TableFieldType, foreign: TableFieldType) -> str: + """gets the table name n:m-relations intermediate table""" + if (f"{own.table}_{own.column}") < (f"{foreign.table}_{foreign.column}"): + return f"nm_{own.table}_{HelperGetNames.get_initial_letters(own.column)}_{foreign.table}_t" + else: + return f"nm_{foreign.table}_{HelperGetNames.get_initial_letters(foreign.column)}_{own.table}_t" + + @staticmethod + @max_length + def get_gm_table_name(table_field: TableFieldType) -> str: + """ + Gets the table name for generic-list:many-relations intermediate table + Does not deliver correct results on non-primary relations. + """ + return f"gm_{table_field.table}_{table_field.column}_t" + + @staticmethod + @max_length + def get_field_in_n_m_relation_list( + own_table_field: TableFieldType, foreign_table_name: str + ) -> str: + """gets the field name in a n:m-intermediate table. + If both sides of the relation are in same table, the field name without 's' is used, + otherwise the related tables names are used + """ + if own_table_field.table == foreign_table_name: + return own_table_field.intermediate_column + else: + return f"{own_table_field.table}_id" + + @staticmethod + @max_length + def get_gm_content_field(table: str, field: str) -> str: + """Gets the name of content field in an generic:many intermediate table""" + return f"{table}_{field}_id" + + @staticmethod + @max_length + def get_generic_valid_constraint_name( + fname: str, + ) -> str: + """gets the name of a generic valid constraint""" + return f"valid_{fname}_part1" + + @staticmethod + @max_length + def get_generic_unique_constraint_name( + own_table_name_with_ref_column: str, own_table_column: str + ) -> str: + """gets the name of a generic unique constraint + Params: + - {table_name}_{ref_column} + - {owcolumn} + """ + return f"unique_${own_table_name_with_ref_column}_${own_table_column}" + + @staticmethod + @max_length + def get_check_enum_constraint_name( + table_name: str, + fname: str, + ) -> str: + """gets the name of check enum constraint""" + return f"enum_{table_name}_{fname}" + + @staticmethod + @max_length + def get_minimum_constraint_name( + fname: str, + ) -> str: + """gets the name of minimum constraint""" + return f"minimum_{fname}" + + @staticmethod + @max_length + def get_minlength_constraint_name( + fname: str, + ) -> str: + """gets the name of minLength constraint""" + return f"minlength_{fname}" + + @staticmethod + @max_length + def get_not_null_insert_trigger_name_base( + table_name: str, + column_name: str, + ) -> str: + """gets the name of the insert trigger for not null""" + name = f"tr_i_{table_name}_{column_name}"[: HelperGetNames.MAX_LEN] + if name in HelperGetNames.trigger_unique_list: + raise Exception(f"trigger {name} is not unique!") + HelperGetNames.trigger_unique_list.append(name) + return name + + @staticmethod + @max_length + def get_not_null_upd_del_trigger_name_base( + table_name: str, + column_name: str, + ) -> str: + """gets the name of the update/delete trigger for not null""" + name = f"tr_ud_{table_name}_{column_name}"[: HelperGetNames.MAX_LEN] + if name in HelperGetNames.trigger_unique_list: + raise Exception(f"trigger {name} is not unique!") + HelperGetNames.trigger_unique_list.append(name) + return name + + @staticmethod + @max_length + def get_not_null_rel_list_insert_trigger_name( + table_name: str, + column_name: str, + ) -> str: + """gets the name of the insert trigger for not null on relation lists""" + return HelperGetNames.get_not_null_insert_trigger_name_base( + table_name, column_name + ) + + @staticmethod + @max_length + def get_not_null_rel_list_upd_del_trigger_name( + table_name: str, + column_name: str, + ) -> str: + """gets the name of the update/delete trigger for not null on relation lists""" + return HelperGetNames.get_not_null_upd_del_trigger_name_base( + table_name, column_name + ) + + @staticmethod + @max_length + def get_not_null_1_1_rel_insert_trigger_name( + table_name: str, + column_name: str, + ) -> str: + """gets the name of the insert trigger for not null on 1:1 relations""" + return HelperGetNames.get_not_null_insert_trigger_name_base( + table_name, column_name + ) + + @staticmethod + @max_length + def get_not_null_1_1_rel_upd_del_trigger_name( + table_name: str, + column_name: str, + ) -> str: + """gets the name of the update/delete trigger for not null on 1:1 relations""" + return HelperGetNames.get_not_null_upd_del_trigger_name_base( + table_name, column_name + ) + + @staticmethod + @max_length + def get_notify_trigger_name( + table_name: str, + ) -> str: + """gets the name of the trigger for logging changes on models""" + name = f"tr_log_{table_name}"[: HelperGetNames.MAX_LEN] + if name in HelperGetNames.trigger_unique_list: + raise Exception(f"trigger {name} is not unique!") + HelperGetNames.trigger_unique_list.append(name) + return name + + @staticmethod + @max_length + def get_notify_related_trigger_name( + table_name: str, + column_name: str, + ) -> str: + """gets the name of the trigger for logging changes on related models""" + name = f"tr_log_{table_name}_{column_name}"[: HelperGetNames.MAX_LEN] + if name in HelperGetNames.trigger_unique_list: + raise Exception(f"trigger {name} is not unique!") + HelperGetNames.trigger_unique_list.append(name) + return name + + +class InternalHelper: + MODELS: dict[str, dict[str, Any]] = {} + checksum: str = "" + ref_compiled = compiled = re.compile(r"(^\w+\b).*?\((.*?)\)") + + @classmethod + def read_models_yml(cls, file: str) -> tuple[dict[str, Any], str]: + """method reads models.yml from file or web and returns MODELS and it's checksum""" + if os.path.isfile(file): + with open(file, "rb") as x: + models_yml = x.read() + else: + models_yml = requests.get(file).content + + # calc checksum to assert the schema.sql is up-to-date + checksum = hashlib.md5(models_yml).hexdigest() + + # Fix broken keys + models_yml = models_yml.replace(b" yes:", b' "yes":') + models_yml = models_yml.replace(b" no:", b' "no":') + + # Load and parse models.yml + cls.MODELS = yaml.safe_load(models_yml) + cls.check_field_length() + return cls.MODELS, checksum + + @classmethod + def check_field_length(cls) -> None: + to_long: list[str] = [] + for table_name, fields in cls.MODELS.items(): + if table_name in ["_migration_index", "_meta"]: + continue + for fname in fields.keys(): + if len(fname) > HelperGetNames.MAX_LEN: + to_long.append(f"{table_name}.{fname}:{len(fname)}") + if to_long: + raise Exception("\n".join(to_long)) + + @staticmethod + def get_field_definition_from_to(to: str) -> tuple[str, str, dict[str, Any]]: + try: + tname, fname = to.split("/") + field = InternalHelper.get_models(tname, fname) + except Exception as e: + raise Exception( + f"Exception on splitting to {to} in get_field_definition_from_to: {e}" + ) + assert ( + len(tname) <= HelperGetNames.MAX_LEN + ), f"Generated tname '{tname}' to long in function 'get_field_definition_from_to'!" + assert ( + len(fname) <= HelperGetNames.MAX_LEN + ), f"Generated fname '{fname}' to long in function 'get_field_definition_from_to'!" + + return tname, fname, field + + @staticmethod + def get_foreign_key_table_column(reference: str | None) -> tuple[str, str]: + """ + Returns a tuple (table_name, field_name) gotten from "reference"-attribute + """ + if reference: + result = InternalHelper.ref_compiled.search(reference) + if result is None: + return reference.strip(), "id" + re_groups = result.groups() + cols = re_groups[1] + if cols: + cols = ",".join([col.strip() for col in cols.split(",")]) + else: + cols = "id" + return re_groups[0], cols + else: + raise Exception("Relation field without reference") + + @classmethod + def get_models(cls, collection: str, field: str) -> dict[str, Any]: + if cls.MODELS: + try: + return cls.MODELS[collection][field] + except KeyError: + raise Exception(f"MODELS field {collection}.{field} doesn't exist") + raise Exception("You have to initialize models in class InternalHelper") + + @staticmethod + def get_cardinality(field_all: TableFieldType) -> tuple[str, str]: + """ + Returns + - string with cardinality string (1, 1G, n or nG= Cardinality, G=Generic-relation, r=reference, t=to, s=sql, R=required) + - string with error message or empty string if no error + """ + error = "" + field = field_all.field_def + if field: + required = bool(field.get("required")) + sql = "sql" in field + to = bool(field.get("to")) + reference = bool(field.get("reference")) + + # general rules of inconsistent field descriptions on field level + if reference and not to: # temporary rule to keep all to-attributes + error = "Field with reference temporarily needs also to-attribute\n" + elif field.get("sql") == "": + error = "sql attribute may not be empty\n" + elif required and sql: + error = "Field with attribute sql cannot be required\n" + elif not (to or reference): + error = "Relation field must have `to` or `reference` attribute set\n" + elif field["type"] == "generic-relation-list" and required: + error = "generic-relation-list cannot be required: not implemented\n" + + if field["type"] == "relation": + result = "1" + elif field["type"] == "relation-list": + result = "n" + elif field["type"] == "generic-relation": + result = "1G" + elif field["type"] == "generic-relation-list": + result = "nG" + else: + raise Exception( + f"Not implemented type {field['type']} in method get_cardinality found!" + ) + if reference: + result += "r" + if ( + to and not reference + ): # to with reference only for temporary backup compatibility in backend relation-handling + result += "t" + if required: + result += "R" + if sql: + result += "s" + else: + result = "" + return result, error + + @staticmethod + def generate_field_or_sql_decision( + own: TableFieldType, own_card: str, foreign: TableFieldType, foreign_card: str + ) -> tuple[FieldSqlErrorType, bool, str]: + """ + Returns: + - field, sql, error for own => enum FieldSqlErrorType + - primary field for own: (only relevant for list fields) + - error line if error else empty string + """ + decision_list: dict[ + tuple[str, str], tuple[FieldSqlErrorType | None, bool | str | None] + ] = { + ("1Gr", ""): (FieldSqlErrorType.FIELD, False), + ("1GrR", ""): (FieldSqlErrorType.FIELD, False), + ("1r", ""): (FieldSqlErrorType.FIELD, False), + ("1rR", ""): (FieldSqlErrorType.FIELD, False), + ("1t", "1GrR"): (FieldSqlErrorType.SQL, False), + ("1t", "1r"): (FieldSqlErrorType.SQL, False), + ("1t", "1rR"): (FieldSqlErrorType.SQL, False), + ("1tR", "1Gr"): (FieldSqlErrorType.SQL, False), + ("1tR", "1GrR"): (FieldSqlErrorType.SQL, False), + ("1tR", "1r"): (FieldSqlErrorType.SQL, False), + ("1tR", "1rR"): (FieldSqlErrorType.SQL, False), + ("nGt", "nt"): (FieldSqlErrorType.SQL, True), + ("nr", ""): (FieldSqlErrorType.SQL, True), + ("nt", "1Gr"): (FieldSqlErrorType.SQL, False), + ("nt", "1GrR"): (FieldSqlErrorType.SQL, False), + ("nt", "1r"): (FieldSqlErrorType.SQL, False), + ("nt", "1rR"): (FieldSqlErrorType.SQL, False), + ("nt", "nGt"): (FieldSqlErrorType.SQL, False), + ("nt", "nt"): (FieldSqlErrorType.SQL, "primary_decide_alphabetical"), + ("nt", "ntR"): (FieldSqlErrorType.SQL, "primary_decide_alphabetical"), + ("ntR", "1r"): (FieldSqlErrorType.SQL, False), + ("ntR", "nt"): (FieldSqlErrorType.SQL, "primary_decide_alphabetical"), + ("nts", "nts"): (FieldSqlErrorType.SQL, False), + } + + foreign_card_replacement_list: list[str] = [ + "1Gr", + "1GrR", + "1r", + "1rR", + "nr", + ] + + state: FieldSqlErrorType | str | None + primary: bool | str | None + error = "" + + if own_card in foreign_card_replacement_list: + foreign_card = "" + + state, primary = decision_list.get((own_card, foreign_card), (None, None)) + if state is None: + error = f"Type combination not implemented: {own_card}:{foreign_card} on field {own.collectionfield}\n" + state = FieldSqlErrorType.ERROR + elif primary == "primary_decide_alphabetical": + primary = ( + own.collectionfield == foreign.collectionfield + or foreign.collectionfield == "-" + or own.collectionfield < foreign.collectionfield + ) + return cast(FieldSqlErrorType, state), cast(bool, primary), error + + @staticmethod + def check_relation_definitions( + own_field: TableFieldType, foreign_fields: list[TableFieldType] + ) -> tuple[FieldSqlErrorType, bool, str, str]: + """ + Decides for the own-field, + - whether it is a field, an sql-expression or if there is an error + - relation-list and generic-relation-list are always sql-expressions. + True signifies that it is the primary that creates the intermediate table. + + Also checks relational behaviour and produces the informative relation line and in + case of an error an error text. + + Returns: + - field, sql, error => enum FieldSqlErrorType + - primary field (only relevant for list fields) + - complete relational text with FIELD, SQL or *** in front + - error line if error else empty string + """ + error = "" + own_card, tmp_error = InternalHelper.get_cardinality(own_field) + error = error or tmp_error + foreign_card = [] + foreign_collectionfields = [] + for foreign_field in foreign_fields: + foreign_c, tmp_error = InternalHelper.get_cardinality(foreign_field) + foreign_card.append(foreign_c) + error = error or tmp_error + foreign_collectionfields.append(foreign_field.collectionfield) + + if error: + state = FieldSqlErrorType.ERROR + primary = False + else: + for i, foreign_field in enumerate(foreign_fields): + if i == 0: + state, primary, error = ( + InternalHelper.generate_field_or_sql_decision( + own_field, own_card, foreign_field, foreign_card[i] + ) + ) + else: + statex, primaryx, error = ( + InternalHelper.generate_field_or_sql_decision( + own_field, own_card, foreign_field, foreign_card[i] + ) + ) + if not error and (statex != state or primaryx != primary): + error = f"Error in generation for generic collection field '{own_field.collectionfield}'" + if error: + state = FieldSqlErrorType.ERROR + break + + state_text = "***" if state == FieldSqlErrorType.ERROR else state.name + text = f"{state_text} {own_card}:{','.join(foreign_card)} => {own_field.collectionfield}:-> {','.join(foreign_collectionfields)}\n" + if state == FieldSqlErrorType.ERROR: + text += f" {error}" + return state, primary, text, error + + @staticmethod + def get_definitions_from_foreign_list( + to: ToDict | list[str] | None, + reference: list[str] | None, + ) -> list[TableFieldType]: + """ + used for generic_relation with multiple foreign relations + """ + # temporarily allowed + # if to and reference: + # raise Exception( + # f"Field {table}/{field}: On generic-relation fields it is not allowed to use 'to' and 'reference' for 1 field" + # ) + results: list[TableFieldType] = [] + # precedence for reference + if reference: + for ref in reference: + if isinstance(to, dict): + to_field = f"{ref}/{to['field']}" + elif isinstance(to, list): + for collectionfield in to: + if collectionfield.startswith(ref): + to_field = collectionfield + elif isinstance(to, str): + to_field = to + else: + raise Exception("No valid type for 'to:' provided.") + if not to_field: + raise Exception("Couldn't find foreign field.") + results.append( + TableFieldType.get_definitions_from_foreign(to_field, ref) + ) + elif isinstance(to, dict): + fname = "/" + to["field"] + for table in to["collections"]: + results.append( + TableFieldType.get_definitions_from_foreign(table + fname, None) + ) + elif isinstance(to, list): + for collectionfield in to: + results.append( + TableFieldType.get_definitions_from_foreign(collectionfield, None) + ) + else: + results.append(TableFieldType.get_definitions_from_foreign(to, None)) + return results diff --git a/dev/src/python_sql.py b/dev/src/python_sql.py new file mode 100644 index 00000000..c94d3ace --- /dev/null +++ b/dev/src/python_sql.py @@ -0,0 +1,2 @@ +# mypy does not recognize the imports from `python-sql` correctly. Therefore, we gather them in this file so they can be imported from here in other places without using `type: ignore` everytime. +from sql import Column, Table # type: ignore # noqa:F401 diff --git a/dev/src/validate.py b/dev/src/validate.py index d2745dbe..6200fb1d 100644 --- a/dev/src/validate.py +++ b/dev/src/validate.py @@ -35,6 +35,7 @@ "number", "string[]", "number[]", + "text[]", "boolean", "JSON", "HTMLStrict", @@ -56,6 +57,8 @@ "required", "read_only", "constant", + "unique", + "sequence_scope", ) @@ -196,8 +199,18 @@ def check_field( if field.get("calculated"): return + if field_name := field.get("sequence_scope", ""): + if type != "number": + self.errors.append( + f"Sequences can only be generated for number fields. {collectionfield} is {type}." + ) + if field_name not in self.models[collection]: + self.errors.append( + f"{field_name} can not be used as a source of sequence scope since it is not part of {collection}." + ) + valid_attributes = list(OPTIONAL_ATTRIBUTES) + required_attributes - if type == "string[]": + if type in ["string[]", "text[]"]: valid_attributes.append("items") if "items" in field and "enum" not in field["items"]: self.errors.append( @@ -247,6 +260,7 @@ def check_field( valid_attributes.append("equal_fields") if nested and type in ("relation", "relation-list"): valid_attributes.append("enum") + valid_attributes.extend(("reference", "deferred", "sql")) for attr in field.keys(): if attr not in valid_attributes: @@ -274,7 +288,7 @@ def validate_value_for_type( self.errors.append( f"Value '{value}' for '{collectionfield}' is not a {type_str}." ) - elif type_str in ("string[]", "number[]"): + elif type_str in ("string[]", "number[]", "text[]"): if not isinstance(value, list): self.errors.append( f"Value '{value}' for '{collectionfield}' is not a {type_str}." diff --git a/dev/tests/__init__.py b/dev/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/dev/tests/base.py b/dev/tests/base.py new file mode 100644 index 00000000..fd251827 --- /dev/null +++ b/dev/tests/base.py @@ -0,0 +1,844 @@ +import os +from collections.abc import Callable +from unittest import TestCase + +import psycopg +from psycopg import sql +from psycopg.types.json import Jsonb +from src.db_utils import DbUtils +from src.python_sql import Table + +# ADMIN_USERNAME = "admin" +# ADMIN_PASSWORD = "admin" + + +class BaseTestCase(TestCase): + temporary_template_db = "openslides_template" + work_on_test_db = "openslides_test" + db_connection: psycopg.Connection + + # id's of pre loaded rows, see method populate_database + meeting1_id = 0 + theme1_id = 0 + organization_id = 0 + user1_id = 0 + committee1_id = 0 + groupM1_default_id = 0 + groupM1_admin_id = 0 + groupM1_staff_id = 0 + simple_workflowM1_id = 0 + complex_workflowM1_id = 0 + + @classmethod + def set_db_connection( + cls, + db_name: str, + autocommit: bool = False, + row_factory: Callable = psycopg.rows.dict_row, + ) -> None: + env = os.environ + try: + cls.db_connection = psycopg.connect( + f"dbname='{db_name}' user='{env['DATABASE_USER']}' host='{env['DATABASE_HOST']}' password='{env['PGPASSWORD']}'", + autocommit=autocommit, + row_factory=row_factory, + ) + cls.db_connection.isolation_level = psycopg.IsolationLevel.SERIALIZABLE + except Exception as e: + raise Exception(f"Cannot connect to postgres: {e.message}") + + @classmethod + def setup_class(cls) -> None: + env = os.environ + cls.set_db_connection("postgres", True) + with cls.db_connection: + with cls.db_connection.cursor() as curs: + curs.execute( + sql.SQL( + "DROP DATABASE IF EXISTS {temporary_template_db} (FORCE);" + ).format( + temporary_template_db=sql.Identifier(cls.temporary_template_db) + ) + ) + curs.execute( + sql.SQL( + "CREATE DATABASE {db_to_create} TEMPLATE {template_db};" + ).format( + db_to_create=sql.Identifier(cls.temporary_template_db), + template_db=sql.Identifier(env["DATABASE_NAME"]), + ) + ) + cls.set_db_connection(cls.temporary_template_db) + with cls.db_connection: + with cls.db_connection.cursor() as curs: + curs.execute("CREATE EXTENSION pldbgapi;") # Postgres debug extension + cls.populate_database() + + @classmethod + def teardown_class(cls) -> None: + """remove last test db and drop the temporary template db""" + cls.set_db_connection("postgres", True) + with cls.db_connection: + with cls.db_connection.cursor() as curs: + curs.execute( + sql.SQL("DROP DATABASE IF EXISTS {} (FORCE);").format( + sql.Identifier(cls.work_on_test_db) + ) + ) + curs.execute( + sql.SQL("DROP DATABASE IF EXISTS {} (FORCE);").format( + sql.Identifier(cls.temporary_template_db) + ) + ) + + def setUp(self) -> None: + self.set_db_connection("postgres", autocommit=True) + with self.db_connection: + with self.db_connection.cursor() as curs: + curs.execute( + sql.SQL("DROP DATABASE IF EXISTS {} (FORCE);").format( + sql.Identifier(self.work_on_test_db) + ) + ) + curs.execute( + sql.SQL( + "CREATE DATABASE {test_db} TEMPLATE {temporary_template_db};" + ).format( + test_db=sql.Identifier(self.work_on_test_db), + temporary_template_db=sql.Identifier( + self.temporary_template_db + ), + ) + ) + + self.set_db_connection(self.work_on_test_db) + + @classmethod + def populate_database(cls) -> None: + """do something like setting initial_data.json""" + theme_t = Table("theme_t") + organization_t = Table("organization_t") + user_t = Table("user_t") + committee_t = Table("committee_t") + + with cls.db_connection.transaction(): + with cls.db_connection.cursor() as curs: + cls.theme1_id = curs.execute( + *theme_t.insert( + columns=[ + theme_t.name, + theme_t.accent_500, + theme_t.primary_500, + theme_t.warn_500, + ], + values=[["OpenSlides Blue", "#2196f3", "#317796", "#f06400"]], + returning=[theme_t.id], + ) + ).fetchone()["id"] + data = [ + { + "name": "Test Organization", + "legal_notice": 'OpenSlides is a free web based presentation and assembly system for visualizing and controlling agenda, motions and elections of an assembly.', + "login_text": "Good Morning!", + "default_language": "en", + "genders": ["male", "female", "diverse", "non-binary"], + "enable_electronic_voting": True, + "enable_chat": True, + "reset_password_verbose_errors": True, + "limit_of_meetings": 0, + "limit_of_users": 0, + "theme_id": cls.theme1_id, + "users_email_sender": "OpenSlides", + "users_email_subject": "OpenSlides access data", + "users_email_body": "Dear {name},\n\nthis is your personal OpenSlides login:\n\n{url}\nUsername: {username}\nPassword: {password}\n\n\nThis email was generated automatically.", + "url": "https://example.com", + "saml_enabled": False, + "saml_login_button_text": "SAML Login", + "saml_attr_mapping": Jsonb( + { + "saml_id": "username", + "title": "title", + "first_name": "firstName", + "last_name": "lastName", + "email": "email", + "gender": "gender", + "pronoun": "pronoun", + "is_active": "is_active", + "is_physical_person": "is_person", + } + ), + } + ] + columns, values = DbUtils.get_columns_and_values_for_insert( + organization_t, data + ) + cls.organization_id = curs.execute( + *organization_t.insert( + columns, values, returning=[organization_t.id] + ) + ).fetchone()["id"] + data = [ + { + "username": "admin", + "last_name": "Administrator", + "is_active": True, + "is_physical_person": True, + "password": "316af7b2ddc20ead599c38541fbe87e9a9e4e960d4017d6e59de188b41b2758flD5BVZAZ8jLy4nYW9iomHcnkXWkfk3PgBjeiTSxjGG7+fBjMBxsaS1vIiAMxYh+K38l0gDW4wcP+i8tgoc4UBg==", + "default_password": "admin", + "can_change_own_password": True, + "gender": "male", + "default_vote_weight": "1.000000", + "organization_management_level": "superadmin", + } + ] + columns, values = DbUtils.get_columns_and_values_for_insert( + user_t, data + ) + cls.user1_id = curs.execute( + *user_t.insert(columns, values, returning=[user_t.id]) + ).fetchone()["id"] + cls.committee1_id = curs.execute( + *committee_t.insert( + columns=[committee_t.name, committee_t.description], + values=[["Default committee", "Add description here"]], + returning=[committee_t.id], + ) + ).fetchone()["id"] + result_ids = cls.create_meeting(curs, committee_id=cls.committee1_id) + cls.meeting1_id = result_ids["meeting_id"] + cls.groupM1_default_id = result_ids["default_group_id"] + cls.groupM1_admin_id = result_ids["admin_group_id"] + cls.groupM1_staff_id = result_ids["staff_group_id"] + cls.simple_workflowM1_id = result_ids["simple_workflow_id"] + cls.complex_workflowM1_id = result_ids["complex_workflow_id"] + curs.execute( + *committee_t.update( + columns=[committee_t.default_meeting_id], + values=[cls.committee1_id], + ) + ) + + @classmethod + def create_meeting( + cls, + curs: psycopg.Cursor, + committee_id: int, + meeting_id: int = 0, + ) -> dict[str, int]: + """ + Creates meeting with next availale id if not set or id set (lower ids can't be choosed afterwards) + The committee_id must be given and the committee must exist. The meeting will not be set as default meeting of the committee + 3 groups with permissions (Default, Admin, Staff) were created + 2 projectors (Default projector, Secondary projector) were created + 2 workflows (simple, complex were created) + Return Value dict of ids with keys + - meeting_id + - default_group_id, admin_group_id, staff_group_id + - default_project_id, secondary_projector_id + - simple_workflow_id, complex_workflow_id + """ + group_t = Table("group_t") + projector_t = Table("projector_t") + motion_state_t = Table("motion_state_t") + nm_motion_state_next_state_ids_motion_state_t = Table( + "nm_motion_state_next_state_ids_motion_state_t" + ) + motion_workflow_t = Table("motion_workflow_t") + meeting_t = Table("meeting_t") + + result = {} + if meeting_id: + sequence_name = curs.execute( + "select * from pg_get_serial_sequence('meeting_t', 'id');" + ).fetchone()["pg_get_serial_sequence"] + last_value = curs.execute( + f"select last_value from {sequence_name};" + ).fetchone()["last_value"] + if last_value >= meeting_id: + raise ValueError( + f"meeting_id {meeting_id} is not available, last_value in sequence {sequence_name} is {last_value}" + ) + result["meeting_id"] = curs.execute( + "select setval(pg_get_serial_sequence('meeting_t', 'id'), %s);", + (meeting_id,), + ).fetchone()["setval"] + else: + result["meeting_id"] = curs.execute( + "select nextval(pg_get_serial_sequence('meeting_t', 'id')) as id_;" + ).fetchone()["id_"] + curs.execute( + *group_t.insert( + columns=[ + group_t.name, + group_t.permissions, + group_t.weight, + group_t.meeting_id, + ], + values=[ + [ + "Default", + [ + "agenda_item.can_see_internal", + "assignment.can_see", + "list_of_speakers.can_see", + "mediafile.can_see", + "meeting.can_see_frontpage", + "motion.can_see", + "projector.can_see", + "user.can_see", + ], + 1, + result["meeting_id"], + ], + ["Admin", [], 2, result["meeting_id"]], + [ + "Staff", + [ + "agenda_item.can_manage", + "assignment.can_manage", + "assignment.can_nominate_self", + "list_of_speakers.can_be_speaker", + "list_of_speakers.can_manage", + "mediafile.can_manage", + "meeting.can_see_frontpage", + "meeting.can_see_history", + "motion.can_manage", + "poll.can_manage", + "projector.can_manage", + "tag.can_manage", + "user.can_manage", + ], + 3, + result["meeting_id"], + ], + ], + returning=[group_t.id], + ) + ) + ( + result["default_group_id"], + result["admin_group_id"], + result["staff_group_id"], + ) = (x["id"] for x in curs) + data = [ + { + "name": "Default projector", + "is_internal": False, + "scale": 0, + "scroll": 0, + "width": 1220, + "aspect_ratio_numerator": 4, + "aspect_ratio_denominator": 3, + "color": "#000000", + "background_color": "#ffffff", + "header_background_color": "#317796", + "header_font_color": "#f5f5f5", + "header_h1_color": "#317796", + "chyron_background_color": "#317796", + "chyron_font_color": "#ffffff", + "show_header_footer": True, + "show_title": True, + "show_logo": True, + "show_clock": True, + "used_as_default_projector_for_agenda_item_list_in_meeting_id": result[ + "meeting_id" + ], + "used_as_default_projector_for_topic_in_meeting_id": result[ + "meeting_id" + ], + "used_as_default_projector_for_list_of_speakers_in_meeting_id": result[ + "meeting_id" + ], + "used_as_default_projector_for_current_los_in_meeting_id": result[ + "meeting_id" + ], + "used_as_default_projector_for_motion_in_meeting_id": result[ + "meeting_id" + ], + "used_as_default_projector_for_amendment_in_meeting_id": result[ + "meeting_id" + ], + "used_as_default_projector_for_motion_block_in_meeting_id": result[ + "meeting_id" + ], + "used_as_default_projector_for_assignment_in_meeting_id": result[ + "meeting_id" + ], + "used_as_default_projector_for_mediafile_in_meeting_id": result[ + "meeting_id" + ], + "used_as_default_projector_for_message_in_meeting_id": result[ + "meeting_id" + ], + "used_as_default_projector_for_countdown_in_meeting_id": result[ + "meeting_id" + ], + "used_as_default_projector_for_assignment_poll_in_meeting_id": result[ + "meeting_id" + ], + "used_as_default_projector_for_motion_poll_in_meeting_id": result[ + "meeting_id" + ], + "used_as_default_projector_for_poll_in_meeting_id": result[ + "meeting_id" + ], + "meeting_id": result["meeting_id"], + }, + { + "name": "Secondary projector", + "is_internal": False, + "scale": 0, + "scroll": 0, + "width": 1024, + "aspect_ratio_numerator": 16, + "aspect_ratio_denominator": 9, + "color": "#000000", + "background_color": "#888888", + "header_background_color": "#317796", + "header_font_color": "#f5f5f5", + "header_h1_color": "#317796", + "chyron_background_color": "#317796", + "chyron_font_color": "#ffffff", + "show_header_footer": True, + "show_title": True, + "show_logo": True, + "show_clock": True, + "meeting_id": result["meeting_id"], + }, + ] + columns, values = DbUtils.get_columns_and_values_for_insert(projector_t, data) + curs.execute(*projector_t.insert(columns, values, returning=[projector_t.id])) + (result["default_projector_id"], result["secondary_projector_id"]) = ( + x["id"] for x in curs + ) + + result["simple_workflow_id"] = curs.execute( + "select nextval(pg_get_serial_sequence('motion_workflow_t', 'id')) as new_id;" + ).fetchone()["new_id"] + result["complex_workflow_id"] = curs.execute( + "select nextval(pg_get_serial_sequence('motion_workflow_t', 'id')) as new_id;" + ).fetchone()["new_id"] + motion_state_data = [ + { + "name": "submitted", + "weight": 1, + "css_class": "lightblue", + "allow_support": True, + "allow_create_poll": True, + "allow_submitter_edit": True, + "set_number": True, + "merge_amendment_into_final": "undefined", + "workflow_id": result["simple_workflow_id"], + "restrictions": [], + "show_state_extension_field": False, + "show_recommendation_extension_field": False, + "set_workflow_timestamp": True, + "allow_motion_forwarding": True, + "meeting_id": result["meeting_id"], + }, + { + "name": "accepted", + "weight": 2, + "recommendation_label": "Acceptance", + "css_class": "green", + "set_number": True, + "merge_amendment_into_final": "undefined", + "workflow_id": result["simple_workflow_id"], + "restrictions": [], + "show_state_extension_field": False, + "show_recommendation_extension_field": False, + "allow_submitter_edit": False, + "allow_create_poll": False, + "allow_support": False, + "set_workflow_timestamp": False, + "allow_motion_forwarding": True, + "meeting_id": result["meeting_id"], + }, + { + "name": "rejected", + "weight": 3, + "recommendation_label": "Rejection", + "css_class": "red", + "set_number": True, + "merge_amendment_into_final": "undefined", + "workflow_id": result["simple_workflow_id"], + "restrictions": [], + "show_state_extension_field": False, + "show_recommendation_extension_field": False, + "allow_submitter_edit": False, + "allow_create_poll": False, + "allow_support": False, + "allow_motion_forwarding": True, + "set_workflow_timestamp": False, + "meeting_id": result["meeting_id"], + }, + { + "name": "not decided", + "weight": 4, + "recommendation_label": "No decision", + "css_class": "grey", + "set_number": True, + "merge_amendment_into_final": "undefined", + "workflow_id": result["simple_workflow_id"], + "restrictions": [], + "show_state_extension_field": False, + "show_recommendation_extension_field": False, + "allow_submitter_edit": False, + "allow_create_poll": False, + "allow_support": False, + "allow_motion_forwarding": True, + "set_workflow_timestamp": False, + "meeting_id": result["meeting_id"], + }, + ] + columns, values = DbUtils.get_columns_and_values_for_insert( + motion_state_t, motion_state_data + ) + curs.execute( + *motion_state_t.insert(columns, values, returning=[motion_state_t.id]) + ) + wf_m1_simple_motion_state_ids = [x["id"] for x in curs] + wf_m1_simple_first_state_id = wf_m1_simple_motion_state_ids[0] + + motion_state_data = [ + { + "name": "in progress", + "weight": 5, + "css_class": "lightblue", + "set_number": False, + "allow_submitter_edit": True, + "merge_amendment_into_final": "undefined", + "workflow_id": result["complex_workflow_id"], + "restrictions": [], + "show_state_extension_field": False, + "show_recommendation_extension_field": False, + "allow_create_poll": False, + "allow_support": False, + "set_workflow_timestamp": True, + "allow_motion_forwarding": True, + "meeting_id": result["meeting_id"], + }, + { + "name": "submitted", + "weight": 6, + "css_class": "lightblue", + "set_number": False, + "merge_amendment_into_final": "undefined", + "workflow_id": result["complex_workflow_id"], + "restrictions": [], + "show_state_extension_field": False, + "show_recommendation_extension_field": False, + "allow_submitter_edit": False, + "allow_create_poll": False, + "allow_support": True, + "allow_motion_forwarding": True, + "set_workflow_timestamp": False, + "meeting_id": result["meeting_id"], + }, + { + "name": "permitted", + "weight": 7, + "recommendation_label": "Permission", + "css_class": "lightblue", + "set_number": True, + "merge_amendment_into_final": "undefined", + "workflow_id": result["complex_workflow_id"], + "restrictions": [], + "show_state_extension_field": False, + "show_recommendation_extension_field": False, + "allow_submitter_edit": False, + "allow_create_poll": True, + "allow_support": False, + "allow_motion_forwarding": True, + "set_workflow_timestamp": False, + "meeting_id": 1, + }, + { + "name": "accepted", + "weight": 8, + "recommendation_label": "Acceptance", + "css_class": "green", + "set_number": True, + "merge_amendment_into_final": "do_merge", + "workflow_id": result["complex_workflow_id"], + "restrictions": [], + "show_state_extension_field": False, + "show_recommendation_extension_field": False, + "allow_submitter_edit": False, + "allow_create_poll": False, + "allow_support": False, + "allow_motion_forwarding": True, + "set_workflow_timestamp": False, + "meeting_id": result["meeting_id"], + }, + { + "name": "rejected", + "weight": 9, + "recommendation_label": "Rejection", + "css_class": "red", + "set_number": True, + "merge_amendment_into_final": "do_not_merge", + "workflow_id": result["complex_workflow_id"], + "restrictions": [], + "show_state_extension_field": False, + "show_recommendation_extension_field": False, + "allow_submitter_edit": False, + "allow_create_poll": False, + "allow_support": False, + "allow_motion_forwarding": True, + "set_workflow_timestamp": False, + "meeting_id": result["meeting_id"], + }, + { + "name": "withdrawn", + "weight": 10, + "css_class": "grey", + "set_number": True, + "merge_amendment_into_final": "do_not_merge", + "workflow_id": result["complex_workflow_id"], + "restrictions": [], + "show_state_extension_field": False, + "show_recommendation_extension_field": False, + "allow_submitter_edit": False, + "allow_create_poll": False, + "allow_support": False, + "allow_motion_forwarding": True, + "set_workflow_timestamp": False, + "meeting_id": result["meeting_id"], + }, + { + "name": "adjourned", + "weight": 11, + "recommendation_label": "Adjournment", + "css_class": "grey", + "set_number": True, + "merge_amendment_into_final": "do_not_merge", + "workflow_id": result["complex_workflow_id"], + "restrictions": [], + "show_state_extension_field": False, + "show_recommendation_extension_field": False, + "allow_submitter_edit": False, + "allow_create_poll": False, + "allow_support": False, + "allow_motion_forwarding": True, + "set_workflow_timestamp": False, + "meeting_id": result["meeting_id"], + }, + { + "name": "not concerned", + "weight": 12, + "recommendation_label": "No concernment", + "css_class": "grey", + "set_number": True, + "merge_amendment_into_final": "do_not_merge", + "workflow_id": result["complex_workflow_id"], + "restrictions": [], + "show_state_extension_field": False, + "show_recommendation_extension_field": False, + "allow_submitter_edit": False, + "allow_create_poll": False, + "allow_support": False, + "allow_motion_forwarding": True, + "set_workflow_timestamp": False, + "meeting_id": result["meeting_id"], + }, + { + "name": "referred to committee", + "weight": 13, + "recommendation_label": "Referral to committee", + "css_class": "grey", + "set_number": True, + "merge_amendment_into_final": "do_not_merge", + "workflow_id": result["complex_workflow_id"], + "restrictions": [], + "show_state_extension_field": False, + "show_recommendation_extension_field": False, + "allow_submitter_edit": False, + "allow_create_poll": False, + "allow_support": False, + "allow_motion_forwarding": True, + "set_workflow_timestamp": False, + "meeting_id": result["meeting_id"], + }, + { + "name": "needs review", + "weight": 14, + "css_class": "grey", + "set_number": True, + "merge_amendment_into_final": "do_not_merge", + "workflow_id": result["complex_workflow_id"], + "restrictions": [], + "show_state_extension_field": False, + "show_recommendation_extension_field": False, + "allow_submitter_edit": False, + "allow_create_poll": False, + "allow_support": False, + "allow_motion_forwarding": True, + "set_workflow_timestamp": False, + "meeting_id": result["meeting_id"], + }, + { + "name": "rejected (not authorized)", + "weight": 15, + "recommendation_label": "Rejection (not authorized)", + "css_class": "grey", + "set_number": True, + "merge_amendment_into_final": "do_not_merge", + "workflow_id": result["complex_workflow_id"], + "restrictions": [], + "show_state_extension_field": False, + "show_recommendation_extension_field": False, + "allow_submitter_edit": False, + "allow_create_poll": False, + "allow_support": False, + "allow_motion_forwarding": True, + "set_workflow_timestamp": False, + "meeting_id": result["meeting_id"], + }, + ] + columns, values = DbUtils.get_columns_and_values_for_insert( + motion_state_t, motion_state_data + ) + curs.execute( + *motion_state_t.insert(columns, values, returning=[motion_state_t.id]) + ) + wf_m1_complex_motion_state_ids = [x["id"] for x in curs] + wf_m1_complex_first_state_id = wf_m1_complex_motion_state_ids[0] + + data = [ + { + "next_state_id": wf_m1_simple_motion_state_ids[1], + "previous_state_id": wf_m1_simple_motion_state_ids[0], + }, + { + "next_state_id": wf_m1_simple_motion_state_ids[2], + "previous_state_id": wf_m1_simple_motion_state_ids[0], + }, + { + "next_state_id": wf_m1_simple_motion_state_ids[3], + "previous_state_id": wf_m1_simple_motion_state_ids[0], + }, + { + "next_state_id": wf_m1_complex_motion_state_ids[1], + "previous_state_id": wf_m1_complex_motion_state_ids[0], + }, + { + "next_state_id": wf_m1_complex_motion_state_ids[5], + "previous_state_id": wf_m1_complex_motion_state_ids[0], + }, + { + "next_state_id": wf_m1_complex_motion_state_ids[2], + "previous_state_id": wf_m1_complex_motion_state_ids[1], + }, + { + "next_state_id": wf_m1_complex_motion_state_ids[5], + "previous_state_id": wf_m1_complex_motion_state_ids[1], + }, + { + "next_state_id": wf_m1_complex_motion_state_ids[10], + "previous_state_id": wf_m1_complex_motion_state_ids[1], + }, + { + "next_state_id": wf_m1_complex_motion_state_ids[3], + "previous_state_id": wf_m1_complex_motion_state_ids[2], + }, + { + "next_state_id": wf_m1_complex_motion_state_ids[4], + "previous_state_id": wf_m1_complex_motion_state_ids[2], + }, + { + "next_state_id": wf_m1_complex_motion_state_ids[5], + "previous_state_id": wf_m1_complex_motion_state_ids[2], + }, + { + "next_state_id": wf_m1_complex_motion_state_ids[6], + "previous_state_id": wf_m1_complex_motion_state_ids[2], + }, + { + "next_state_id": wf_m1_complex_motion_state_ids[7], + "previous_state_id": wf_m1_complex_motion_state_ids[2], + }, + { + "next_state_id": wf_m1_complex_motion_state_ids[8], + "previous_state_id": wf_m1_complex_motion_state_ids[2], + }, + { + "next_state_id": wf_m1_complex_motion_state_ids[9], + "previous_state_id": wf_m1_complex_motion_state_ids[2], + }, + ] + columns, values = DbUtils.get_columns_and_values_for_insert( + nm_motion_state_next_state_ids_motion_state_t, data + ) + curs.execute( + *nm_motion_state_next_state_ids_motion_state_t.insert(columns, values) + ) + + data = [ + { + "id": result["simple_workflow_id"], + "name": "Simple Workflow", + "first_state_id": wf_m1_simple_first_state_id, + "meeting_id": result["meeting_id"], + }, + { + "id": result["complex_workflow_id"], + "name": "Complex Workflow", + "first_state_id": wf_m1_complex_first_state_id, + "meeting_id": result["meeting_id"], + }, + ] + columns, values = DbUtils.get_columns_and_values_for_insert( + motion_workflow_t, data + ) + curs.execute( + *motion_workflow_t.insert(columns, values, returning=[motion_workflow_t.id]) + ) + assert [ + result["simple_workflow_id"], + result["complex_workflow_id"], + ] == [x["id"] for x in curs] + + data = [ + { + "id": result["meeting_id"], + "name": "OpenSlides Demo", + "is_active_in_organization_id": cls.organization_id, + "language": "en", + "conference_los_restriction": True, + "agenda_number_prefix": "TOP", + "motions_default_workflow_id": result["simple_workflow_id"], + "motions_default_amendment_workflow_id": result["complex_workflow_id"], + "motions_default_statute_amendment_workflow_id": result[ + "complex_workflow_id" + ], + "motions_recommendations_by": "ABK", + "motions_statute_recommendations_by": "", + "motions_statutes_enabled": True, + "motions_amendments_of_amendments": True, + "motions_amendments_prefix": "-\u00c4", + "motions_supporters_min_amount": 1, + "motions_export_preamble": "", + "users_enable_presence_view": True, + "users_pdf_wlan_encryption": "", + "users_enable_vote_delegations": True, + "poll_ballot_paper_selection": "CUSTOM_NUMBER", + "poll_ballot_paper_number": 8, + "poll_sort_poll_result_by_votes": True, + "poll_default_type": "nominal", + "poll_default_method": "votes", + "poll_default_onehundred_percent_base": "valid", + "committee_id": committee_id, + "reference_projector_id": result["default_projector_id"], + "default_group_id": result["default_group_id"], + "admin_group_id": result["admin_group_id"], + }, + ] + columns, values = DbUtils.get_columns_and_values_for_insert(meeting_t, data) + assert ( + result["meeting_id"] + == curs.execute( + *meeting_t.insert(columns, values, returning=[meeting_t.id]) + ).fetchone()["id"] + ) + return result diff --git a/dev/tests/test_generic_relations.py b/dev/tests/test_generic_relations.py new file mode 100644 index 00000000..92de6c22 --- /dev/null +++ b/dev/tests/test_generic_relations.py @@ -0,0 +1,1026 @@ +from typing import Any + +import psycopg +import pytest +from psycopg import sql +from src.db_utils import DbUtils +from src.python_sql import Table +from tests.base import BaseTestCase + +agenda_item_t = Table("agenda_item_t") +assignment_t = Table("assignment_t") +assignment_v = Table("assignment") +committee_v = Table("committee") +gm_organization_tag_tagged_ids_t = Table("gm_organization_tag_tagged_ids_t") +group_v = Table("group_") +group_t = Table("group_t") +list_of_speakers_t = Table("list_of_speakers_t") +list_of_speakers_v = Table("list_of_speakers") +mediafile_t = Table("mediafile_t") +mediafile_v = Table("mediafile") +meeting_t = Table("meeting_t") +meeting_v = Table("meeting") +option_t = Table("option_t") +organization_tag_t = Table("organization_tag_t") +organization_v = Table("organization") +poll_candidate_list_t = Table("poll_candidate_list_t") +poll_candidate_list_v = Table("poll_candidate_list") +projector_t = Table("projector") +theme_v = Table("theme") +user_v = Table("user_") + + +class Relations(BaseTestCase): + """ + Used symbols in names of test: + 1: cardinality 1 + 1G: cardinality 1 with generic-relation field + n: cardinality n + nG: cardinality n with generic-relation-list field + t: "to" defined + r: "reference" defined + s: sql directive given, but must be generated + s+: sql directive inclusive sql-statement + R: Required + """ + + """ 1:n relation tests with n-side NOT NULL """ + """ Test:motion_state.submitter_withdraw_back_ids: sql okay?""" + """ 1:1 relation tests """ + + # todo: 1Gr errors + def test_generic_1Gr_check_constraint_error(self) -> None: + """tries to use a not defined owner-model for generic field owner_id""" + with pytest.raises(psycopg.DatabaseError) as e: + with self.db_connection.cursor() as curs: + with self.db_connection.transaction(): + curs.execute( + *mediafile_t.insert( + [mediafile_t.is_public, mediafile_t.owner_id], + [[True, f"motion_state/{self.meeting1_id}"]], + ) + ) + assert ( + 'new row for relation "mediafile_t" violates check constraint "valid_owner_id_part1"' + in str(e) + ) + + # todo: 1GrR errors + # todo: 1r errors + # todo: 1rR errors + + # todo: 1t:1GrR + def test_o2o_generic_1t_1GrR_okay(self) -> None: + """SQL 1t:1GrR => assignment/agenda_item_id:-> agenda_item/content_object_id""" + with self.db_connection.cursor() as curs: + with self.db_connection.transaction(): + assignment_id = curs.execute( + *assignment_t.insert( + [ + assignment_t.title, + assignment_t.meeting_id, + ], + [["title assignment 1", self.meeting1_id]], + returning=[assignment_t.id], + ) + ).fetchone()["id"] + assignment = f"assignment/{assignment_id}" + agenda_item_id = curs.execute( + *agenda_item_t.insert( + [ + agenda_item_t.item_number, + agenda_item_t.content_object_id, + agenda_item_t.meeting_id, + ], + [["100", assignment, self.meeting1_id]], + returning=[agenda_item_t.id], + ) + ).fetchone()["id"] + assignment_row = curs.execute( + *assignment_v.select( + where=assignment_v.agenda_item_id == agenda_item_id + ) + ).fetchone() + agenda_item_row = curs.execute( + *agenda_item_t.select( + where=agenda_item_t.content_object_id == assignment + ) + ).fetchone() + assert assignment_row["agenda_item_id"] == agenda_item_row["id"] + assert agenda_item_row["content_object_id"] == assignment + assert ( + agenda_item_row["content_object_id_assignment_id"] == 1 + ) # internal storage + + def test_o2o_generic_1t_1GrR_okay_with_setval(self) -> None: + with self.db_connection.cursor() as curs: + with self.db_connection.transaction(): + mediafile_id = 2 + los_id = 3 + curs.execute( + "select setval(pg_get_serial_sequence('mediafile_t', 'id'), %s);", + (mediafile_id,), + ) + assert ( + mediafile_id + == curs.execute( + *mediafile_t.insert( + [ + mediafile_t.id, + mediafile_t.is_public, + mediafile_t.owner_id, + ], + [[mediafile_id, True, f"meeting/{self.meeting1_id}"]], + returning=[mediafile_t.id], + ) + ).fetchone()["id"] + ) + curs.execute( + "select setval(pg_get_serial_sequence('list_of_speakers_t', 'id'), %s);", + (los_id,), + ) + data = [ + { + "id": los_id, + "content_object_id": ( + content_object_id := f"mediafile/{mediafile_id}" + ), + "meeting_id": self.meeting1_id, + }, + ] + columns, values = DbUtils.get_columns_and_values_for_insert( + list_of_speakers_t, data + ) + assert ( + los_id + == curs.execute( + *list_of_speakers_t.insert( + columns, values, returning=[list_of_speakers_t.id] + ) + ).fetchone()["id"] + ) + los_row: dict[str, Any] = curs.execute( + *list_of_speakers_t.select( + list_of_speakers_t.id, + list_of_speakers_t.content_object_id, + list_of_speakers_t.content_object_id_mediafile_id, + list_of_speakers_t.content_object_id_topic_id, + where=list_of_speakers_t.id == los_id, + ) + ).fetchone() + assert los_row["id"] == los_id + assert los_row["content_object_id"] == content_object_id + assert los_row["content_object_id_mediafile_id"] == mediafile_id + assert los_row["content_object_id_topic_id"] is None + + mediafile_row = curs.execute( + *mediafile_v.select( + mediafile_v.id, + mediafile_v.list_of_speakers_id, + mediafile_v.owner_id, + where=mediafile_v.id == mediafile_id, + ) + ).fetchone() + assert mediafile_row["id"] == mediafile_id + assert mediafile_row["list_of_speakers_id"] == los_id + + # todo: 1t:1r + def test_o2o_pre_populated_1t_1r_okay(self) -> None: + with self.db_connection.cursor() as curs: + committee_row = curs.execute( + *committee_v.select( + committee_v.default_meeting_id, + where=committee_v.id == self.committee1_id, + ) + ).fetchone() + assert committee_row["default_meeting_id"] == self.meeting1_id + meeting_row = curs.execute( + *meeting_v.select( + meeting_v.default_meeting_for_committee_id, + where=meeting_v.id == self.meeting1_id, + ) + ).fetchone() + assert meeting_row["default_meeting_for_committee_id"] == self.committee1_id + + # todo: 1t:1rR + def test_o2o_pre_populated_1t_1rR_okay(self) -> None: + with self.db_connection.cursor() as curs: + organization_row = curs.execute( + *organization_v.select( + organization_v.theme_id, + where=organization_v.id == self.organization_id, + ) + ).fetchone() + assert organization_row["theme_id"] == self.theme1_id + theme_row = curs.execute( + *theme_v.select( + theme_v.theme_for_organization_id, + where=theme_v.id == self.theme1_id, + ) + ).fetchone() + assert theme_row["theme_for_organization_id"] == self.organization_id + + def test_o2o_1t_1rR_okay_with_change_data(self) -> None: + with self.db_connection.cursor() as curs: + # Prepopulated + meeting_row = curs.execute( + *meeting_v.select( + meeting_v.default_group_id, where=meeting_v.id == self.meeting1_id + ) + ).fetchone() + old_default_group_id = meeting_row["default_group_id"] + old_default_group_row = curs.execute( + *group_v.select( + group_v.default_group_for_meeting_id, + where=group_v.id == old_default_group_id, + ) + ).fetchone() + assert ( + old_default_group_row["default_group_for_meeting_id"] + == self.meeting1_id + ) + # change default group + with self.db_connection.transaction(): + group_staff_row = curs.execute( + *group_v.select( + group_v.id, + group_v.name, + group_v.meeting_id, + group_v.default_group_for_meeting_id, + where=( + (group_v.name == "Staff") + & (group_v.meeting_id == self.meeting1_id) + ), + ) + ).fetchone() + assert group_staff_row["id"] == self.groupM1_staff_id + assert group_staff_row["name"] == "Staff" + assert group_staff_row["meeting_id"] == self.meeting1_id + assert group_staff_row["default_group_for_meeting_id"] is None + curs.execute( + *meeting_t.update( + [meeting_t.default_group_id], + [group_staff_row["id"]], + where=meeting_t.id == self.meeting1_id, + ) + ) + + # assert new and old data + meeting_row = curs.execute( + *meeting_v.select( + meeting_v.default_group_id, where=meeting_v.id == self.meeting1_id + ) + ).fetchone() + assert meeting_row["default_group_id"] == group_staff_row["id"] + new_default_group_row = curs.execute( + *group_v.select( + group_v.default_group_for_meeting_id, + where=group_v.id == group_staff_row["id"], + ) + ).fetchone() + assert ( + new_default_group_row["default_group_for_meeting_id"] + == self.meeting1_id + ) + old_default_group_row = curs.execute( + *group_v.select( + group_v.default_group_for_meeting_id, + where=group_v.id == old_default_group_id, + ) + ).fetchone() + assert old_default_group_row["default_group_for_meeting_id"] is None + + # todo: 1tR:1Gr + def test_o2o_generic_1tR_1Gr_okay_with_setval(self) -> None: + with self.db_connection.cursor() as curs: + with self.db_connection.transaction(): + pcl_id = 2 + option_id = 3 + curs.execute( + "select setval(pg_get_serial_sequence('poll_candidate_list_t', 'id'), %s);", + (pcl_id,), + ) + assert ( + pcl_id + == curs.execute( + *poll_candidate_list_t.insert( + [ + poll_candidate_list_t.id, + poll_candidate_list_t.meeting_id, + ], + [[pcl_id, self.meeting1_id]], + returning=[poll_candidate_list_t.id], + ) + ).fetchone()["id"] + ) + curs.execute( + "select setval(pg_get_serial_sequence('option_t', 'id'), %s);", + (option_id,), + ) + data = [ + { + "id": option_id, + "content_object_id": ( + content_object_id := f"poll_candidate_list/{pcl_id}" + ), + "meeting_id": self.meeting1_id, + }, + ] + columns, values = DbUtils.get_columns_and_values_for_insert( + option_t, data + ) + assert ( + option_id + == curs.execute( + *option_t.insert(columns, values, returning=[option_t.id]) + ).fetchone()["id"] + ) + columns = DbUtils.get_columns_from_list( + option_t, + [ + "id", + "content_object_id", + "content_object_id_poll_candidate_list_id", + "content_object_id_user_id", + ], + ) + option_row = curs.execute( + *option_t.select(*columns, where=option_t.id == option_id) + ).fetchone() + assert option_row["id"] == option_id + assert option_row["content_object_id"] == content_object_id + assert option_row["content_object_id_poll_candidate_list_id"] == pcl_id + assert option_row["content_object_id_user_id"] is None + + pcl_row = curs.execute( + *poll_candidate_list_v.select( + poll_candidate_list_v.id, + poll_candidate_list_v.option_id, + where=poll_candidate_list_v.id == pcl_id, + ) + ).fetchone() + assert pcl_row["id"] == pcl_id + assert pcl_row["option_id"] == option_id + + # todo: 1tR:1GrR to implement R:R + def test_generic_1tR_1GrR_okay(self) -> None: + with self.db_connection.cursor() as curs: + with self.db_connection.transaction(): + assignment_id = 2 + los_id = 3 + curs.execute( + "select setval(pg_get_serial_sequence('assignment_t', 'id'), %s);", + (assignment_id,), + ) + columns, values = DbUtils.get_columns_and_values_for_insert( + assignment_t, + [ + { + "id": assignment_id, + "title": "I am an assignment", + "meeting_id": self.meeting1_id, + }, + ], + ) + assert ( + assignment_id + == curs.execute( + *assignment_t.insert( + columns, values, returning=[assignment_t.id] + ) + ).fetchone()["id"] + ) + + curs.execute( + "select setval(pg_get_serial_sequence('list_of_speakers_t', 'id'), %s);", + (los_id,), + ) + columns, values = DbUtils.get_columns_and_values_for_insert( + list_of_speakers_t, + [ + { + "id": los_id, + "content_object_id": ( + content_object_id := f"assignment/{assignment_id}" + ), + "meeting_id": self.meeting1_id, + } + ], + ) + assert ( + los_id + == curs.execute( + *list_of_speakers_t.insert( + columns, values, returning=[list_of_speakers_t.id] + ) + ).fetchone()["id"] + ) + + los_row = curs.execute( + *list_of_speakers_v.select( + list_of_speakers_v.id, + list_of_speakers_v.content_object_id, + where=list_of_speakers_v.id == los_id, + ) + ).fetchone() + assert los_row["id"] == los_id + assert los_row["content_object_id"] == content_object_id + + assignment_row = curs.execute( + *assignment_v.select( + assignment_v.id, + assignment_v.list_of_speakers_id, + where=assignment_v.id == assignment_id, + ) + ).fetchone() + assert assignment_row["id"] == assignment_id + assert assignment_row["list_of_speakers_id"] == los_id + + # todo: nGt:nt only error check nGt + def test_generic_nGt_check_constraint_error(self) -> None: + with pytest.raises(psycopg.DatabaseError) as e: + with self.db_connection.cursor() as curs: + with self.db_connection.transaction(): + tag_id = curs.execute( + *organization_tag_t.insert( + [organization_tag_t.name, organization_tag_t.color], + [["Orga Tag 1", "#ffee13"]], + returning=[organization_tag_t.id], + ) + ).fetchone()["id"] + columns, values = DbUtils.get_columns_and_values_for_insert( + gm_organization_tag_tagged_ids_t, + [ + { + "organization_tag_id": tag_id, + "tagged_id": f"committee/{self.committee1_id}", + }, + { + "organization_tag_id": tag_id, + "tagged_id": f"motion_state/{self.meeting1_id}", + }, + ], + ) + curs.execute( + *gm_organization_tag_tagged_ids_t.insert(columns, values) + ) + assert ( + 'new row for relation "gm_organization_tag_tagged_ids_t" violates check constraint "valid_tagged_id_part1"' + in str(e) + ) + + def test_generic_nGt_unique_constraint_error(self) -> None: + with self.db_connection.cursor() as curs: + with self.db_connection.transaction(): + tag_id = curs.execute( + *organization_tag_t.insert( + [organization_tag_t.name, organization_tag_t.color], + [["Orga Tag 1", "#ffee13"]], + returning=[organization_tag_t.id], + ) + ).fetchone()["id"] + curs.execute( + *gm_organization_tag_tagged_ids_t.insert( + [ + gm_organization_tag_tagged_ids_t.organization_tag_id, + gm_organization_tag_tagged_ids_t.tagged_id, + ], + [[tag_id, f"committee/{self.committee1_id}"]], + returning=[gm_organization_tag_tagged_ids_t.id], + ) + ).fetchone()["id"] + with pytest.raises(psycopg.DatabaseError) as e: + with self.db_connection.transaction(): + curs.execute( + *gm_organization_tag_tagged_ids_t.insert( + [ + gm_organization_tag_tagged_ids_t.organization_tag_id, + gm_organization_tag_tagged_ids_t.tagged_id, + ], + [[tag_id, f"committee/{self.committee1_id}"]], + returning=[gm_organization_tag_tagged_ids_t.id], + ) + ) + assert "duplicate key value violates unique constraint" in str(e) + + # todo: nr + # todo: nt:1Gr + def test_generic_nt_1Gr(self) -> None: + with self.db_connection.cursor() as curs: + with self.db_connection.transaction(): + option_id = 3 + curs.execute( + "select setval(pg_get_serial_sequence('poll_candidate_list_t', 'id'), %s);", + (option_id,), + ) + columns, values = DbUtils.get_columns_and_values_for_insert( + option_t, + [ + { + "id": option_id, + "content_object_id": ( + content_object_id := f"user/{self.user1_id}" + ), + "meeting_id": self.meeting1_id, + }, + ], + ) + option_id = curs.execute( + *option_t.insert(columns, values, returning=[option_t.id]) + ).fetchone()["id"] + option_row = curs.execute( + *option_t.select( + *DbUtils.get_columns_from_list( + option_t, + [ + "id", + "content_object_id", + "content_object_id_user_id", + "content_object_id_poll_candidate_list_id", + ], + ), + where=option_t.id == option_id, + ) + ).fetchone() + assert option_row["id"] == option_id + assert option_row["content_object_id"] == content_object_id + assert option_row["content_object_id_user_id"] == self.user1_id + assert option_row["content_object_id_poll_candidate_list_id"] is None + + user_row = curs.execute( + *user_v.select( + user_v.username, user_v.option_ids, where=user_v.id == self.user1_id + ) + ).fetchone() + assert user_row["option_ids"] == [option_id] + assert user_row["username"] == "admin" + + # todo: nt:1GrR + def test_o2m_generic_nt_1GrR_okay(self) -> None: + with self.db_connection.cursor() as curs: + with self.db_connection.transaction(): + columns, values = DbUtils.get_columns_and_values_for_insert( + mediafile_t, + [ + {"is_public": True, "owner_id": f"meeting/{self.meeting1_id}"}, + { + "is_public": True, + "owner_id": f"organization/{self.organization_id}", + }, + {"is_public": True, "owner_id": f"meeting/{self.meeting1_id}"}, + { + "is_public": True, + "owner_id": f"organization/{self.organization_id}", + }, + ], + ) + curs.execute(*mediafile_t.insert(columns, values)) + rows = curs.execute( + *mediafile_v.select( + mediafile_v.owner_id, + mediafile_v.owner_id_meeting_id, + mediafile_v.owner_id_organization_id, + ) + ).fetchall() + expected_results = ( + ("meeting/1", 1, None), + ("organization/1", None, 1), + ("meeting/1", 1, None), + ("organization/1", None, 1), + ) + for i, row in enumerate(rows): + assert tuple(row.values()) == expected_results[i] + + meeting_row = curs.execute( + *meeting_v.select( + meeting_v.mediafile_ids, where=meeting_v.id == self.meeting1_id + ) + ).fetchone() + assert meeting_row["mediafile_ids"] == [1, 3] + organization_row = curs.execute( + *organization_v.select(organization_v.mediafile_ids) + ).fetchone() + assert organization_row["mediafile_ids"] == [2, 4] + + # todo: nt:1r + # todo: nt:1rR + # todo: nt:nGt + def test_n2m_generic_nt_nGt_okay(self) -> None: + with self.db_connection.cursor() as curs: + with self.db_connection.transaction(): + columns, values = DbUtils.get_columns_and_values_for_insert( + organization_tag_t, + [ + {"name": "Orga Tag 1", "color": "#ffee13"}, + {"name": "Orga Tag 2", "color": "#12ee13"}, + {"name": "Orga Tag 3", "color": "#00ee13"}, + ], + ) + + curs.execute( + *organization_tag_t.insert( + columns, values, returning=[organization_tag_t.id] + ) + ) + tag_ids = [x["id"] for x in curs] + columns, values = DbUtils.get_columns_and_values_for_insert( + gm_organization_tag_tagged_ids_t, + [ + { + "organization_tag_id": tag_ids[0], + "tagged_id": f"committee/{self.committee1_id}", + }, + { + "organization_tag_id": tag_ids[0], + "tagged_id": f"meeting/{self.meeting1_id}", + }, + { + "organization_tag_id": tag_ids[1], + "tagged_id": f"committee/{self.committee1_id}", + }, + { + "organization_tag_id": tag_ids[2], + "tagged_id": f"meeting/{self.meeting1_id}", + }, + ], + ) + curs.execute(*gm_organization_tag_tagged_ids_t.insert(columns, values)) + rows = curs.execute( + *gm_organization_tag_tagged_ids_t.select( + *DbUtils.get_columns_from_list( + gm_organization_tag_tagged_ids_t, + [ + "id", + "organization_tag_id", + "tagged_id", + "tagged_id_committee_id", + "tagged_id_meeting_id", + ], + ) + ) + ).fetchall() + expected_results = ( + (1, 1, "committee/1", 1, None), + (2, 1, "meeting/1", None, 1), + (3, 2, "committee/1", 1, None), + (4, 3, "meeting/1", None, 1), + ) + for i, row in enumerate(rows): + assert tuple(row.values()) == expected_results[i] + + committee_row = curs.execute( + *committee_v.select( + committee_v.organization_tag_ids, + where=committee_v.id == self.committee1_id, + ) + ).fetchone() + assert committee_row["organization_tag_ids"] == [1, 2] + meeting_row = curs.execute( + *meeting_v.select( + meeting_v.organization_tag_ids, + where=meeting_v.id == self.meeting1_id, + ) + ).fetchone() + assert meeting_row["organization_tag_ids"] == [1, 3] + + # todo: nt:nt + # todo: ntR:1r + def test_o2m_ntR_1r_update_okay(self) -> None: + """Update sets new default projector before 2nd removes old default projector""" + with self.db_connection.cursor() as curs: + projector_ids = curs.execute( + *projector_t.select( + projector_t.id, where=projector_t.meeting_id == self.meeting1_id + ) + ).fetchall() + with self.db_connection.transaction(): + curs.execute( + *projector_t.update( + [projector_t.used_as_default_projector_for_topic_in_meeting_id], + [self.meeting1_id], + where=projector_t.id == [projector_ids[1]["id"]], + ) + ) + curs.execute( + *projector_t.update( + [projector_t.used_as_default_projector_for_topic_in_meeting_id], + [None], + where=projector_t.id == [projector_ids[0]["id"]], + ) + ) + assert ( + projector_ids[1]["id"] + == curs.execute( + *meeting_v.select( + meeting_v.default_projector_topic_ids, + where=meeting_v.id == self.meeting1_id, + ) + ).fetchone()["default_projector_topic_ids"][0] + ) + + def test_o2m_ntR_1r_update_error(self) -> None: + """update removes default projector => Exception""" + with self.db_connection.cursor() as curs: + with pytest.raises(psycopg.errors.RaiseException) as e: + projector_id = curs.execute( + *projector_t.select( + projector_t.id, + where=projector_t.used_as_default_projector_for_topic_in_meeting_id + == self.meeting1_id, + ) + ).fetchone()["id"] + with self.db_connection.transaction(): + curs.execute( + *projector_t.update( + [ + projector_t.used_as_default_projector_for_topic_in_meeting_id + ], + [ + None, + ], + where=projector_t.id == projector_id, + ) + ) + assert ( + "Exception: NOT NULL CONSTRAINT VIOLATED for meeting.default_projector_topic_ids" + in str(e) + ) + + def test_o2m_ntR_1r_delete_error(self) -> None: + """delete projector from meeting => Exception""" + with self.db_connection.cursor() as curs: + projector_id = curs.execute( + "SELECT id from projector where used_as_default_projector_for_topic_in_meeting_id = %s", + (self.meeting1_id,), + ).fetchone()["id"] + with pytest.raises(psycopg.errors.RaiseException) as e: + with self.db_connection.transaction(): + curs.execute( + sql.SQL("DELETE FROM projector where id = %s;"), (projector_id,) + ) + assert "Exception: NOT NULL CONSTRAINT VIOLATED" in str(e) + + def test_o2m_ntR_1r_insert_delete_okay(self) -> None: + """first insert, than delete old default projector from meeting => okay""" + with self.db_connection.cursor() as curs: + with self.db_connection.transaction(): + columns = DbUtils.get_columns_from_list( + projector_t, + [ + "id", + "meeting_id", + "used_as_default_projector_for_agenda_item_list_in_meeting_id", + "used_as_default_projector_for_topic_in_meeting_id", + "used_as_default_projector_for_list_of_speakers_in_meeting_id", + "used_as_default_projector_for_current_los_in_meeting_id", + "used_as_default_projector_for_motion_in_meeting_id", + "used_as_default_projector_for_amendment_in_meeting_id", + "used_as_default_projector_for_motion_block_in_meeting_id", + "used_as_default_projector_for_assignment_in_meeting_id", + "used_as_default_projector_for_mediafile_in_meeting_id", + "used_as_default_projector_for_message_in_meeting_id", + "used_as_default_projector_for_countdown_in_meeting_id", + "used_as_default_projector_for_assignment_poll_in_meeting_id", + "used_as_default_projector_for_motion_poll_in_meeting_id", + "used_as_default_projector_for_poll_in_meeting_id", + ], + ) + projector: dict[str, Any] = curs.execute( + *projector_t.select( + *columns, + where=projector_t.used_as_default_projector_for_topic_in_meeting_id + == self.meeting1_id, + ) + ).fetchone() + projector_old_id = projector.pop("id") + columns, values = DbUtils.get_columns_and_values_for_insert( + projector_t, [projector] + ) + projector_new_id = curs.execute( + *projector_t.insert(columns, values, returning=[projector_t.id]) + ).fetchone()["id"] + curs.execute( + *meeting_t.update( + [meeting_t.reference_projector_id], [projector_new_id] + ) + ) + curs.execute( + *projector_t.delete(where=projector_t.id == projector_old_id) + ) + assert ( + projector_new_id + == curs.execute( + *meeting_v.select( + meeting_v.default_projector_topic_ids, + where=projector_new_id == meeting_v.reference_projector_id, + ) + ).fetchone()["default_projector_topic_ids"][0] + ) + + # todo: nts:nts + + +class EnumTests(BaseTestCase): + def test_correct_singular_values_in_meeting(self) -> None: + meeting_t = Table("meeting_t") + with self.db_connection.cursor() as curs: + with self.db_connection.transaction(): + meeting = curs.execute( + *meeting_t.select( + meeting_t.language, + meeting_t.export_pdf_fontsize, + where=meeting_t.id == 1, + ) + ).fetchone() + assert meeting["language"] == "en" + assert meeting["export_pdf_fontsize"] == 10 + meeting = curs.execute( + *meeting_t.update( + [meeting_t.language, meeting_t.export_pdf_fontsize], + ["de", 11], + where=meeting_t.id == 1, + returning=[meeting_t.id, meeting_t.language], + ) + ).fetchone() + assert meeting["language"] == "de" + + def test_wrong_language_in_meeting(self) -> None: + meeting_t = Table("meeting_t") + with self.db_connection.cursor() as curs: + with pytest.raises(psycopg.DatabaseError) as e: + with self.db_connection.transaction(): + curs.execute( + *meeting_t.update( + [meeting_t.language], ["xx"], where=meeting_t.id == 1 + ) + ) + assert 'violates check constraint "enum_meeting_language"' in str(e) + + def test_wrong_pdf_fontsize_in_meeting(self) -> None: + meeting_t = Table("meeting_t") + with self.db_connection.cursor() as curs: + with pytest.raises(psycopg.DatabaseError) as e: + with self.db_connection.transaction(): + curs.execute( + *meeting_t.update( + [meeting_t.export_pdf_fontsize], + [22], + where=meeting_t.id == 1, + ) + ) + assert 'violates check constraint "enum_meeting_export_pdf_fontsize"' in str(e) + + def test_correct_permissions_in_group(self) -> None: + group_t = Table("group_t") + with self.db_connection.cursor() as curs: + with self.db_connection.transaction(): + group: dict[str, Any] = curs.execute( + *group_t.select(group_t.permissions, where=group_t.id == 1) + ).fetchone() + assert "agenda_item.can_see_internal" in group["permissions"] + assert "user.can_see" in group["permissions"] + assert "chat.can_manage" not in group["permissions"] + group["permissions"].remove("user.can_see") + group["permissions"].append("chat.can_manage") + sql = tuple( + group_t.update( + [group_t.permissions], + [ + DbUtils.get_pg_array_for_cu(group["permissions"]), + ], + where=group_t.id == 1, + returning=[group_t.permissions], + ) + ) + group = curs.execute(*sql).fetchone() + assert "agenda_item.can_see_internal" in group["permissions"] + assert "user.can_see" not in group["permissions"] + assert "chat.can_manage" in group["permissions"] + + def test_wrong_permissions_in_group(self) -> None: + group_t = Table("group_t") + with self.db_connection.cursor() as curs: + with self.db_connection.transaction(): + with pytest.raises(psycopg.DatabaseError) as e: + group = {"permissions": ["user.can_see", "invalid permission"]} + sql = tuple( + group_t.update( + [group_t.permissions], + [ + DbUtils.get_pg_array_for_cu(group["permissions"]), + ], + where=group_t.id == 1, + returning=[group_t.permissions], + ) + ) + group = curs.execute(*sql).fetchone() + assert 'violates check constraint "enum_group_permissions"' in str(e) + + +class DataTypeTests(BaseTestCase): + def test_color_type_correct(self) -> None: + orga_tag_t = Table("organization_tag_t") + with self.db_connection.cursor() as curs: + with self.db_connection.transaction(): + orga_tags = curs.execute( + *orga_tag_t.insert( + columns=[orga_tag_t.name, orga_tag_t.color], + values=[["Foo", "#ff12cc"], ["Bar", "#1234AA"]], + returning=[orga_tag_t.id, orga_tag_t.name, orga_tag_t.color], + ) + ).fetchall() + assert orga_tags[0] == {"id": 1, "name": "Foo", "color": "#ff12cc"} + assert orga_tags[1] == {"id": 2, "name": "Bar", "color": "#1234AA"} + + def test_color_type_not_null_error(self) -> None: + orga_tag_t = Table("organization_tag_t") + with self.db_connection.cursor() as curs: + with self.db_connection.transaction(): + with pytest.raises(psycopg.DatabaseError) as e: + curs.execute( + *orga_tag_t.insert( + columns=[orga_tag_t.name, orga_tag_t.color], + values=[["Foo", None]], + returning=[ + orga_tag_t.id, + orga_tag_t.name, + orga_tag_t.color, + ], + ) + ).fetchone() + assert ( + 'null value in column "color" of relation "organization_tag_t" violates not-null constraint' + in str(e) + ) + + def test_color_type_null_correct(self) -> None: + sl_t = Table("structure_level_t") + with self.db_connection.cursor() as curs: + with self.db_connection.transaction(): + sl_id = curs.execute( + *sl_t.insert( + columns=[sl_t.name, sl_t.color, sl_t.meeting_id], + values=[["Foo", None, 1]], + returning=[sl_t.id], + ) + ).fetchone()["id"] + structure_level = curs.execute( + *sl_t.select(sl_t.id, sl_t.color, where=sl_t.id == sl_id) + ).fetchone() + assert structure_level == {"id": sl_id, "color": None} + + def test_color_type_empty_string_error(self) -> None: + sl_t = Table("structure_level_t") + with self.db_connection.cursor() as curs: + with self.db_connection.transaction(): + with pytest.raises(psycopg.DatabaseError) as e: + curs.execute( + *sl_t.insert( + columns=[sl_t.name, sl_t.color, sl_t.meeting_id], + values=[["Foo", "", 1]], + returning=[sl_t.id], + ) + ).fetchone()["id"] + assert ( + """new row for relation "structure_level_t" violates check constraint "structure_level_t_color_check""" + in str(e) + ) + + def test_color_type_wrong_string_error(self) -> None: + sl_t = Table("structure_level_t") + with self.db_connection.cursor() as curs: + with self.db_connection.transaction(): + with pytest.raises(psycopg.DatabaseError) as e: + curs.execute( + *sl_t.insert( + columns=[sl_t.name, sl_t.color, sl_t.meeting_id], + values=[["Foo", "xxx", 1]], + returning=[sl_t.id], + ) + ).fetchone()["id"] + assert ( + """new row for relation "structure_level_t" violates check constraint "structure_level_t_color_check""" + in str(e) + ) + + def test_color_type_to_long_string_error(self) -> None: + sl_t = Table("structure_level_t") + with self.db_connection.cursor() as curs: + with self.db_connection.transaction(): + with pytest.raises(psycopg.DatabaseError) as e: + curs.execute( + *sl_t.insert( + columns=[sl_t.name, sl_t.color, sl_t.meeting_id], + values=[["Foo", "#1234567", 1]], + returning=[sl_t.id], + ) + ).fetchone()["id"] + assert """value too long for type character varying(7)""" in str(e) + + +class ManualSqlTests(BaseTestCase): + pass + + +class ConstraintTests(BaseTestCase): + """foreign keys etc.""" diff --git a/models.yml b/models.yml index 63455b69..23cbefd1 100644 --- a/models.yml +++ b/models.yml @@ -117,6 +117,11 @@ agenda_item: restriction_mode: A content_object_id: type: generic-relation + reference: + - motion + - motion_block + - assignment + - topic to: collections: - motion @@ -130,6 +135,7 @@ agenda_item: constant: true parent_id: type: relation + reference: agenda_item to: agenda_item/child_ids equal_fields: meeting_id restriction_mode: A @@ -151,6 +157,7 @@ agenda_item: restriction_mode: A meeting_id: type: relation + reference: meeting to: meeting/agenda_item_ids required: true restriction_mode: A @@ -189,6 +196,7 @@ assignment: restriction_mode: A sequential_number: type: number + sequence_scope: meeting_id description: The (positive) serial number of this model in its meeting. This number is auto-generated and read-only. read_only: true @@ -239,6 +247,7 @@ assignment: restriction_mode: A meeting_id: type: relation + reference: meeting to: meeting/assignment_ids required: true restriction_mode: A @@ -259,6 +268,7 @@ assignment_candidate: restriction_mode: A assignment_id: type: relation + reference: assignment to: assignment/candidate_ids equal_fields: meeting_id restriction_mode: A @@ -266,11 +276,13 @@ assignment_candidate: constant: true meeting_user_id: type: relation + reference: meeting_user to: meeting_user/assignment_candidate_ids restriction_mode: A constant: true meeting_id: type: relation + reference: meeting to: meeting/assignment_candidate_ids required: true restriction_mode: A @@ -307,6 +319,7 @@ chat_group: restriction_mode: A meeting_id: type: relation + reference: meeting to: meeting/chat_group_ids required: true restriction_mode: A @@ -327,17 +340,20 @@ chat_message: restriction_mode: A meeting_user_id: type: relation + reference: meeting_user to: meeting_user/chat_message_ids restriction_mode: A constant: true chat_group_id: type: relation + reference: chat_group to: chat_group/chat_message_ids restriction_mode: A required: true constant: true meeting_id: type: relation + reference: meeting to: meeting/chat_message_ids required: true restriction_mode: A @@ -367,13 +383,23 @@ committee: default_meeting_id: type: relation to: meeting/default_meeting_for_committee_id + reference: meeting restriction_mode: A user_ids: type: relation-list to: user/committee_ids restriction_mode: A read_only: true - description: Calculated field. + description: 'Calculated field: All users which are in a group of a meeting, belonging + to the committee or beeing manager of the committee' + sql: "(\n SELECT array_agg(DISTINCT user_id ORDER BY user_id)\n FROM (\n \ + \ -- Select user_ids from committees meetings\n SELECT mu.user_id\n FROM\ + \ meeting_t AS m\n INNER JOIN meeting_user_t AS mu ON mu.meeting_id = m.id\n\ + \ WHERE m.committee_id = c.id\n\n UNION\n\n -- Select user_ids from\ + \ committee managers\n SELECT cmu.user_id\n FROM nm_committee_manager_ids_user_t\ + \ cmu\n WHERE cmu.committee_id = c.id\n\n UNION\n\n -- Select user_id\ + \ from home committees\n SELECT u.id FROM user_t u WHERE u.home_committee_id\ + \ = c.id\n ) _\n) AS user_ids\n" manager_ids: type: relation-list to: user/committee_management_ids @@ -382,6 +408,7 @@ committee: type: relation to: committee/child_ids restriction_mode: A + reference: committee child_ids: type: relation-list to: committee/parent_id @@ -412,6 +439,7 @@ committee: restriction_mode: A organization_id: type: relation + reference: organization to: organization/committee_ids required: true restriction_mode: A @@ -432,10 +460,12 @@ gender: required: true to: organization/gender_ids restriction_mode: A + reference: organization user_ids: type: relation-list to: user/gender_id restriction_mode: A + reference: user group: id: type: number @@ -559,22 +589,27 @@ group: restriction_mode: A used_as_motion_poll_default_id: type: relation + reference: meeting to: meeting/motion_poll_default_group_ids restriction_mode: A used_as_assignment_poll_default_id: type: relation + reference: meeting to: meeting/assignment_poll_default_group_ids restriction_mode: A used_as_topic_poll_default_id: type: relation + reference: meeting to: meeting/topic_poll_default_group_ids restriction_mode: A used_as_poll_default_id: type: relation + reference: meeting to: meeting/poll_default_group_ids restriction_mode: A meeting_id: type: relation + reference: meeting to: meeting/group_ids required: true restriction_mode: A @@ -601,16 +636,22 @@ history_entry: - assignment field: history_entry_ids restriction_mode: A + reference: + - user + - motion + - assignment position_id: type: relation to: history_position/entry_ids required: true restriction_mode: A constant: true + reference: history_position meeting_id: type: relation to: meeting/relevant_history_entry_ids restriction_mode: A + reference: meeting history_position: id: type: number @@ -629,6 +670,7 @@ history_position: type: relation to: user/history_position_ids restriction_mode: A + reference: user entry_ids: type: relation-list to: history_entry/position_id @@ -677,6 +719,7 @@ list_of_speakers: restriction_mode: A sequential_number: type: number + sequence_scope: meeting_id description: The (positive) serial number of this model in its meeting. This number is auto-generated and read-only. read_only: true @@ -688,6 +731,12 @@ list_of_speakers: restriction_mode: B content_object_id: type: generic-relation + reference: + - motion + - motion_block + - assignment + - topic + - meeting_mediafile to: collections: - motion @@ -720,6 +769,7 @@ list_of_speakers: restriction_mode: A meeting_id: type: relation + reference: meeting to: meeting/list_of_speakers_ids required: true restriction_mode: A @@ -762,9 +812,11 @@ mediafile: published_to_meetings_in_organization_id: type: relation to: organization/published_mediafile_ids + reference: organization restriction_mode: A parent_id: type: relation + reference: mediafile to: mediafile/child_ids equal_fields: owner_id restriction_mode: A @@ -778,6 +830,9 @@ mediafile: to: - meeting/mediafile_ids - organization/mediafile_ids + reference: + - meeting + - organization restriction_mode: A required: true constant: true @@ -806,17 +861,19 @@ meeting: restriction_mode: C name: type: string - maxLength: 100 + maxLength: 200 default: OpenSlides restriction_mode: A required: true is_active_in_organization_id: type: relation + reference: organization to: organization/active_meeting_ids restriction_mode: F description: Backrelation and boolean flag at once is_archived_in_organization_id: type: relation + reference: organization to: organization/archived_meeting_ids restriction_mode: F description: Backrelation and boolean flag at once @@ -863,6 +920,7 @@ meeting: restriction_mode: E template_for_organization_id: type: relation + reference: organization to: organization/template_meeting_ids restriction_mode: B enable_anonymous: @@ -1133,11 +1191,13 @@ meeting: description: 0 disables intervention speakers. motions_default_workflow_id: type: relation + reference: motion_workflow to: motion_workflow/default_workflow_meeting_id required: true restriction_mode: B motions_default_amendment_workflow_id: type: relation + reference: motion_workflow to: motion_workflow/default_amendment_workflow_meeting_id required: true restriction_mode: B @@ -1361,10 +1421,12 @@ meeting: poll_candidate_list_ids: type: relation-list to: poll_candidate_list/meeting_id + reference: poll_candidate_list restriction_mode: B poll_candidate_ids: type: relation-list to: poll_candidate/meeting_id + reference: poll_candidate restriction_mode: B meeting_user_ids: type: relation-list @@ -1683,16 +1745,19 @@ meeting: poll_ids: type: relation-list to: poll/meeting_id + reference: poll on_delete: CASCADE restriction_mode: B option_ids: type: relation-list to: option/meeting_id + reference: option on_delete: CASCADE restriction_mode: B vote_ids: type: relation-list to: vote/meeting_id + reference: vote on_delete: CASCADE restriction_mode: B assignment_ids: @@ -1728,69 +1793,86 @@ meeting: logo_projector_main_id: type: relation to: meeting_mediafile/used_as_logo_projector_main_in_meeting_id + reference: meeting_mediafile restriction_mode: B logo_projector_header_id: type: relation to: meeting_mediafile/used_as_logo_projector_header_in_meeting_id + reference: meeting_mediafile restriction_mode: B logo_web_header_id: type: relation to: meeting_mediafile/used_as_logo_web_header_in_meeting_id + reference: meeting_mediafile restriction_mode: B logo_pdf_header_l_id: type: relation to: meeting_mediafile/used_as_logo_pdf_header_l_in_meeting_id + reference: meeting_mediafile restriction_mode: B logo_pdf_header_r_id: type: relation to: meeting_mediafile/used_as_logo_pdf_header_r_in_meeting_id + reference: meeting_mediafile restriction_mode: B logo_pdf_footer_l_id: type: relation to: meeting_mediafile/used_as_logo_pdf_footer_l_in_meeting_id + reference: meeting_mediafile restriction_mode: B logo_pdf_footer_r_id: type: relation to: meeting_mediafile/used_as_logo_pdf_footer_r_in_meeting_id + reference: meeting_mediafile restriction_mode: B logo_pdf_ballot_paper_id: type: relation to: meeting_mediafile/used_as_logo_pdf_ballot_paper_in_meeting_id + reference: meeting_mediafile restriction_mode: B font_regular_id: type: relation to: meeting_mediafile/used_as_font_regular_in_meeting_id + reference: meeting_mediafile restriction_mode: B font_italic_id: type: relation to: meeting_mediafile/used_as_font_italic_in_meeting_id + reference: meeting_mediafile restriction_mode: B font_bold_id: type: relation to: meeting_mediafile/used_as_font_bold_in_meeting_id + reference: meeting_mediafile restriction_mode: B font_bold_italic_id: type: relation to: meeting_mediafile/used_as_font_bold_italic_in_meeting_id + reference: meeting_mediafile restriction_mode: B font_monospace_id: type: relation to: meeting_mediafile/used_as_font_monospace_in_meeting_id + reference: meeting_mediafile restriction_mode: B font_chyron_speaker_name_id: type: relation to: meeting_mediafile/used_as_font_chyron_speaker_name_in_meeting_id + reference: meeting_mediafile restriction_mode: B font_projector_h1_id: type: relation to: meeting_mediafile/used_as_font_projector_h1_in_meeting_id + reference: meeting_mediafile restriction_mode: B font_projector_h2_id: type: relation to: meeting_mediafile/used_as_font_projector_h2_in_meeting_id + reference: meeting_mediafile restriction_mode: B committee_id: type: relation + reference: committee to: committee/meeting_ids required: true restriction_mode: A @@ -1808,23 +1890,29 @@ meeting: to: user/is_present_in_meeting_ids restriction_mode: B user_ids: - type: number[] + type: relation-list description: Calculated. All user ids from all users assigned to groups of this meeting. read_only: true + sql: "(\n SELECT array_agg(DISTINCT mu.user_id ORDER BY mu.user_id)\n FROM meeting_user_t\ + \ mu\n WHERE mu.meeting_id = m.id\n) AS user_ids\n" + to: user/meeting_ids restriction_mode: A reference_projector_id: type: relation + reference: projector to: projector/used_as_reference_projector_meeting_id required: true restriction_mode: B list_of_speakers_countdown_id: type: relation to: projector_countdown/used_as_list_of_speakers_countdown_meeting_id + reference: projector_countdown restriction_mode: B poll_countdown_id: type: relation to: projector_countdown/used_as_poll_countdown_meeting_id + reference: projector_countdown restriction_mode: B projection_ids: type: relation-list @@ -1903,16 +1991,19 @@ meeting: required: true default_group_id: type: relation + reference: group to: group/default_group_for_meeting_id required: true restriction_mode: B admin_group_id: type: relation to: group/admin_group_for_meeting_id + reference: group restriction_mode: A anonymous_group_id: type: relation to: group/anonymous_group_for_meeting_id + reference: group restriction_mode: B relevant_history_entry_ids: type: relation-list @@ -1927,11 +2018,13 @@ meeting_mediafile: mediafile_id: type: relation to: mediafile/meeting_mediafile_ids + reference: mediafile required: true restriction_mode: A meeting_id: type: relation to: meeting/meeting_mediafile_ids + reference: meeting required: true restriction_mode: A is_public: @@ -2066,12 +2159,14 @@ meeting_user: restriction_mode: E user_id: type: relation + reference: user to: user/meeting_user_ids required: true restriction_mode: C constant: true meeting_id: type: relation + reference: meeting to: meeting/meeting_user_ids required: true restriction_mode: C @@ -2114,6 +2209,7 @@ meeting_user: restriction_mode: A vote_delegated_to_id: type: relation + reference: meeting_user to: meeting_user/vote_delegations_from_ids equal_fields: meeting_id restriction_mode: A @@ -2130,8 +2226,8 @@ meeting_user: group_ids: type: relation-list to: group/meeting_user_ids - equal_fields: meeting_id required: true + equal_fields: meeting_id restriction_mode: A structure_level_ids: type: relation-list @@ -2155,6 +2251,7 @@ motion: restriction_mode: D sequential_number: type: number + sequence_scope: meeting_id description: The (positive) serial number of this model in its meeting. This number is auto-generated and read-only. read_only: true @@ -2223,6 +2320,7 @@ motion: however. Forwarded amendments can also have this field set to false. lead_motion_id: type: relation + reference: motion to: motion/amendment_ids equal_fields: meeting_id restriction_mode: C @@ -2234,6 +2332,7 @@ motion: restriction_mode: C sort_parent_id: type: relation + reference: motion to: motion/sort_child_ids equal_fields: meeting_id restriction_mode: C @@ -2244,10 +2343,12 @@ motion: restriction_mode: C origin_id: type: relation + reference: motion to: motion/derived_motion_ids restriction_mode: A origin_meeting_id: type: relation + reference: meeting to: meeting/forwarded_motion_ids restriction_mode: A derived_motion_ids: @@ -2265,16 +2366,17 @@ motion: identical_motion_ids: type: relation-list to: motion/identical_motion_ids - equal_fields: meeting_id restriction_mode: C state_id: type: relation + reference: motion_state to: motion_state/motion_ids required: true equal_fields: meeting_id restriction_mode: C recommendation_id: type: relation + reference: motion_state to: motion_state/motion_recommendation_ids equal_fields: meeting_id restriction_mode: E @@ -2306,11 +2408,13 @@ motion: restriction_mode: C category_id: type: relation + reference: motion_category to: motion_category/motion_ids equal_fields: meeting_id restriction_mode: C block_id: type: relation + reference: motion_block to: motion_block/motion_ids equal_fields: meeting_id restriction_mode: C @@ -2347,6 +2451,7 @@ motion: option_ids: type: relation-list to: option/content_object_id + reference: option on_delete: CASCADE equal_fields: meeting_id restriction_mode: C @@ -2400,6 +2505,7 @@ motion: restriction_mode: C meeting_id: type: relation + reference: meeting to: meeting/motion_ids required: true restriction_mode: A @@ -2423,6 +2529,7 @@ motion_block: restriction_mode: A sequential_number: type: number + sequence_scope: meeting_id description: The (positive) serial number of this model in its meeting. This number is auto-generated and read-only. read_only: true @@ -2455,6 +2562,7 @@ motion_block: restriction_mode: A meeting_id: type: relation + reference: meeting to: meeting/motion_block_ids required: true restriction_mode: A @@ -2483,6 +2591,7 @@ motion_category: restriction_mode: A sequential_number: type: number + sequence_scope: meeting_id description: The (positive) serial number of this model in its meeting. This number is auto-generated and read-only. read_only: true @@ -2491,6 +2600,7 @@ motion_category: constant: true parent_id: type: relation + reference: motion_category to: motion_category/child_ids equal_fields: meeting_id restriction_mode: A @@ -2506,6 +2616,7 @@ motion_category: restriction_mode: A meeting_id: type: relation + reference: meeting to: meeting/motion_category_ids required: true restriction_mode: A @@ -2553,6 +2664,7 @@ motion_change_recommendation: restriction_mode: A motion_id: type: relation + reference: motion to: motion/change_recommendation_ids required: true equal_fields: meeting_id @@ -2560,6 +2672,7 @@ motion_change_recommendation: constant: true meeting_id: type: relation + reference: meeting to: meeting/motion_change_recommendation_ids required: true restriction_mode: A @@ -2575,6 +2688,7 @@ motion_comment: restriction_mode: A motion_id: type: relation + reference: motion to: motion/comment_ids required: true equal_fields: meeting_id @@ -2582,6 +2696,7 @@ motion_comment: constant: true section_id: type: relation + reference: motion_comment_section to: motion_comment_section/comment_ids required: true equal_fields: meeting_id @@ -2589,6 +2704,7 @@ motion_comment: constant: true meeting_id: type: relation + reference: meeting to: meeting/motion_comment_ids required: true restriction_mode: A @@ -2609,6 +2725,7 @@ motion_comment_section: restriction_mode: A sequential_number: type: number + sequence_scope: meeting_id description: The (positive) serial number of this model in its meeting. This number is auto-generated and read-only. read_only: true @@ -2636,6 +2753,7 @@ motion_comment_section: restriction_mode: A meeting_id: type: relation + reference: meeting to: meeting/motion_comment_section_ids required: true restriction_mode: A @@ -2651,10 +2769,12 @@ motion_editor: restriction_mode: A meeting_user_id: type: relation + reference: meeting_user to: meeting_user/motion_editor_ids restriction_mode: A motion_id: type: relation + reference: motion to: motion/editor_ids equal_fields: meeting_id restriction_mode: A @@ -2662,6 +2782,7 @@ motion_editor: constant: true meeting_id: type: relation + reference: meeting to: meeting/motion_editor_ids required: true restriction_mode: A @@ -2755,6 +2876,7 @@ motion_state: restriction_mode: A submitter_withdraw_state_id: type: relation + reference: motion_state to: motion_state/submitter_withdraw_back_ids equal_fields: - meeting_id @@ -2794,6 +2916,7 @@ motion_state: restriction_mode: A workflow_id: type: relation + reference: motion_workflow to: motion_workflow/state_ids required: true equal_fields: meeting_id @@ -2806,10 +2929,12 @@ motion_state: restriction_mode: A meeting_id: type: relation + reference: meeting to: meeting/motion_state_ids required: true restriction_mode: A constant: true + deferred: true motion_submitter: id: type: number @@ -2821,10 +2946,12 @@ motion_submitter: restriction_mode: A meeting_user_id: type: relation + reference: meeting_user to: meeting_user/motion_submitter_ids restriction_mode: A motion_id: type: relation + reference: motion to: motion/submitter_ids equal_fields: meeting_id restriction_mode: A @@ -2832,6 +2959,7 @@ motion_submitter: constant: true meeting_id: type: relation + reference: meeting to: meeting/motion_submitter_ids required: true restriction_mode: A @@ -2844,10 +2972,12 @@ motion_supporter: required: true meeting_user_id: type: relation + reference: meeting_user to: meeting_user/motion_supporter_ids restriction_mode: A motion_id: type: relation + reference: motion to: motion/supporter_ids equal_fields: meeting_id restriction_mode: A @@ -2855,6 +2985,7 @@ motion_supporter: constant: true meeting_id: type: relation + reference: meeting to: meeting/motion_supporter_ids required: true restriction_mode: A @@ -2871,6 +3002,7 @@ motion_workflow: restriction_mode: A sequential_number: type: number + sequence_scope: meeting_id description: The (positive) serial number of this model in its meeting. This number is auto-generated and read-only. read_only: true @@ -2885,6 +3017,7 @@ motion_workflow: restriction_mode: A first_state_id: type: relation + reference: motion_state to: motion_state/first_state_of_workflow_id required: true equal_fields: meeting_id @@ -2899,6 +3032,7 @@ motion_workflow: restriction_mode: A meeting_id: type: relation + reference: meeting to: meeting/motion_workflow_ids required: true restriction_mode: A @@ -2914,10 +3048,12 @@ motion_working_group_speaker: restriction_mode: A meeting_user_id: type: relation + reference: meeting_user to: meeting_user/motion_working_group_speaker_ids restriction_mode: A motion_id: type: relation + reference: motion to: motion/working_group_speaker_ids equal_fields: meeting_id restriction_mode: A @@ -2925,6 +3061,7 @@ motion_working_group_speaker: constant: true meeting_id: type: relation + reference: meeting to: meeting/motion_working_group_speaker_ids required: true restriction_mode: A @@ -2954,18 +3091,21 @@ option: poll_id: type: relation to: poll/option_ids + reference: poll equal_fields: meeting_id restriction_mode: A constant: true used_as_global_option_in_poll_id: type: relation to: poll/global_option_id + reference: poll equal_fields: meeting_id restriction_mode: A constant: true vote_ids: type: relation-list to: vote/option_id + reference: vote on_delete: CASCADE equal_fields: meeting_id restriction_mode: A @@ -2975,12 +3115,17 @@ option: - motion/option_ids - user/option_ids - poll_candidate_list/option_id + reference: + - motion + - user + - poll_candidate_list equal_fields: meeting_id restriction_mode: A constant: true meeting_id: type: relation to: meeting/option_ids + reference: meeting required: true restriction_mode: A constant: true @@ -3011,6 +3156,7 @@ organization: gender_ids: type: relation-list to: gender/organization_id + reference: gender restriction_mode: A disable_forward_with_attachments: type: boolean @@ -3078,15 +3224,11 @@ organization: saml_private_key: type: text restriction_mode: D - vote_decrypt_public_main_key: - type: string - description: Public key from vote decrypt to validate cryptographic votes. - calculated: true - restriction_mode: A committee_ids: type: relation-list - restriction_mode: B to: committee/organization_id + restriction_mode: B + reference: committee active_meeting_ids: type: relation-list restriction_mode: A @@ -3103,15 +3245,18 @@ organization: type: relation-list restriction_mode: A to: organization_tag/organization_id + reference: organization_tag theme_id: type: relation required: true restriction_mode: A to: theme/theme_for_organization_id + reference: theme theme_ids: type: relation-list restriction_mode: A to: theme/organization_id + reference: theme mediafile_ids: type: relation-list to: mediafile/owner_id @@ -3125,6 +3270,7 @@ organization: type: relation-list restriction_mode: C to: user/organization_id + reference: user users_email_sender: type: string default: OpenSlides @@ -3182,6 +3328,7 @@ organization_tag: restriction_mode: A organization_id: type: relation + reference: organization to: organization/organization_tag_ids restriction_mode: A required: true @@ -3199,6 +3346,7 @@ personal_note: restriction_mode: A meeting_user_id: type: relation + reference: meeting_user to: meeting_user/personal_note_ids equal_fields: meeting_id restriction_mode: A @@ -3206,6 +3354,8 @@ personal_note: constant: true content_object_id: type: generic-relation + reference: + - motion to: collections: - motion @@ -3215,6 +3365,7 @@ personal_note: constant: true meeting_id: type: relation + reference: meeting to: meeting/personal_note_ids required: true restriction_mode: A @@ -3235,6 +3386,7 @@ point_of_order_category: restriction_mode: A meeting_id: type: relation + reference: meeting to: meeting/point_of_order_category_ids required: true restriction_mode: A @@ -3250,13 +3402,13 @@ poll: restriction_mode: A constant: true required: true - description: - type: text - restriction_mode: A title: type: string required: true restriction_mode: A + description: + type: string + restriction_mode: A type: type: string required: true @@ -3363,34 +3515,19 @@ poll: restriction_mode: C sequential_number: type: number + sequence_scope: meeting_id description: The (positive) serial number of this model in its meeting. This number is auto-generated and read-only. read_only: true required: true restriction_mode: A constant: true - crypt_key: - type: string - description: base64 public key to cryptographic votes. - read_only: true - restriction_mode: A - crypt_signature: - type: string - description: base64 signature of cryptographic_key. - read_only: true - restriction_mode: A - votes_raw: - type: text - description: original form of decrypted votes. - read_only: true - restriction_mode: B - votes_signature: - type: string - description: base64 signature of votes_raw field. - read_only: true - restriction_mode: B content_object_id: type: generic-relation + reference: + - motion + - assignment + - topic to: collections: - motion @@ -3404,12 +3541,14 @@ poll: option_ids: type: relation-list to: option/poll_id + reference: option on_delete: CASCADE equal_fields: meeting_id restriction_mode: A global_option_id: type: relation to: option/used_as_global_option_in_poll_id + reference: option on_delete: CASCADE equal_fields: meeting_id restriction_mode: A @@ -3432,6 +3571,7 @@ poll: meeting_id: type: relation to: meeting/poll_ids + reference: meeting restriction_mode: A required: true constant: true @@ -3444,6 +3584,7 @@ poll_candidate: poll_candidate_list_id: type: relation to: poll_candidate_list/poll_candidate_ids + reference: poll_candidate_list equal_fields: meeting_id restriction_mode: A required: true @@ -3451,6 +3592,7 @@ poll_candidate: user_id: type: relation to: user/poll_candidate_ids + reference: user restriction_mode: A constant: true weight: @@ -3460,6 +3602,7 @@ poll_candidate: meeting_id: type: relation to: meeting/poll_candidate_ids + reference: meeting required: true restriction_mode: A constant: true @@ -3472,12 +3615,14 @@ poll_candidate_list: poll_candidate_ids: type: relation-list to: poll_candidate/poll_candidate_list_id + reference: poll_candidate on_delete: CASCADE restriction_mode: A equal_fields: meeting_id meeting_id: type: relation to: meeting/poll_candidate_list_ids + reference: meeting required: true restriction_mode: A constant: true @@ -3513,21 +3658,36 @@ projection: restriction_mode: A current_projector_id: type: relation + reference: projector to: projector/current_projection_ids equal_fields: meeting_id restriction_mode: A preview_projector_id: type: relation + reference: projector to: projector/preview_projection_ids equal_fields: meeting_id restriction_mode: A history_projector_id: type: relation + reference: projector to: projector/history_projection_ids equal_fields: meeting_id restriction_mode: A content_object_id: type: generic-relation + reference: + - meeting + - motion + - meeting_mediafile + - list_of_speakers + - motion_block + - assignment + - agenda_item + - topic + - poll + - projector_message + - projector_countdown to: - meeting/projection_ids - motion/projection_ids @@ -3546,6 +3706,7 @@ projection: constant: true meeting_id: type: relation + reference: meeting to: meeting/all_projection_ids required: true restriction_mode: A @@ -3641,6 +3802,7 @@ projector: restriction_mode: A sequential_number: type: number + sequence_scope: meeting_id description: The (positive) serial number of this model in its meeting. This number is auto-generated and read-only. read_only: true @@ -3671,62 +3833,77 @@ projector: restriction_mode: A used_as_default_projector_for_agenda_item_list_in_meeting_id: type: relation + reference: meeting to: meeting/default_projector_agenda_item_list_ids restriction_mode: A used_as_default_projector_for_topic_in_meeting_id: type: relation + reference: meeting to: meeting/default_projector_topic_ids restriction_mode: A used_as_default_projector_for_list_of_speakers_in_meeting_id: type: relation + reference: meeting to: meeting/default_projector_list_of_speakers_ids restriction_mode: A used_as_default_projector_for_current_los_in_meeting_id: type: relation + reference: meeting to: meeting/default_projector_current_los_ids restriction_mode: A used_as_default_projector_for_motion_in_meeting_id: type: relation + reference: meeting to: meeting/default_projector_motion_ids restriction_mode: A used_as_default_projector_for_amendment_in_meeting_id: type: relation + reference: meeting to: meeting/default_projector_amendment_ids restriction_mode: A used_as_default_projector_for_motion_block_in_meeting_id: type: relation + reference: meeting to: meeting/default_projector_motion_block_ids restriction_mode: A used_as_default_projector_for_assignment_in_meeting_id: type: relation + reference: meeting to: meeting/default_projector_assignment_ids restriction_mode: A used_as_default_projector_for_mediafile_in_meeting_id: type: relation + reference: meeting to: meeting/default_projector_mediafile_ids restriction_mode: A used_as_default_projector_for_message_in_meeting_id: type: relation + reference: meeting to: meeting/default_projector_message_ids restriction_mode: A used_as_default_projector_for_countdown_in_meeting_id: type: relation + reference: meeting to: meeting/default_projector_countdown_ids restriction_mode: A used_as_default_projector_for_assignment_poll_in_meeting_id: type: relation + reference: meeting to: meeting/default_projector_assignment_poll_ids restriction_mode: A used_as_default_projector_for_motion_poll_in_meeting_id: type: relation + reference: meeting to: meeting/default_projector_motion_poll_ids restriction_mode: A used_as_default_projector_for_poll_in_meeting_id: type: relation + reference: meeting to: meeting/default_projector_poll_ids restriction_mode: A meeting_id: type: relation + reference: meeting to: meeting/projector_ids restriction_mode: A required: true @@ -3772,6 +3949,7 @@ projector_countdown: restriction_mode: A meeting_id: type: relation + reference: meeting to: meeting/projector_countdown_ids restriction_mode: A required: true @@ -3793,6 +3971,7 @@ projector_message: restriction_mode: A meeting_id: type: relation + reference: meeting to: meeting/projector_message_ids restriction_mode: A required: true @@ -3845,6 +4024,7 @@ speaker: constant: true list_of_speakers_id: type: relation + reference: list_of_speakers to: list_of_speakers/speaker_ids required: true equal_fields: meeting_id @@ -3854,19 +4034,23 @@ speaker: type: relation equal_fields: meeting_id restriction_mode: A + reference: structure_level_list_of_speakers to: structure_level_list_of_speakers/speaker_ids meeting_user_id: type: relation + reference: meeting_user to: meeting_user/speaker_ids equal_fields: meeting_id restriction_mode: A point_of_order_category_id: type: relation + reference: point_of_order_category to: point_of_order_category/speaker_ids equal_fields: meeting_id restriction_mode: A meeting_id: type: relation + reference: meeting to: meeting/speaker_ids required: true restriction_mode: A @@ -3901,6 +4085,7 @@ structure_level: meeting_id: type: relation required: true + reference: meeting to: meeting/structure_level_ids restriction_mode: A structure_level_list_of_speakers: @@ -3914,12 +4099,14 @@ structure_level_list_of_speakers: required: true equal_fields: meeting_id restriction_mode: A + reference: structure_level to: structure_level/structure_level_list_of_speakers_ids list_of_speakers_id: type: relation required: true equal_fields: meeting_id restriction_mode: A + reference: list_of_speakers to: list_of_speakers/structure_level_list_of_speakers_ids speaker_ids: type: relation-list @@ -3948,6 +4135,7 @@ structure_level_list_of_speakers: only set if a currently speaking speaker exists meeting_id: type: relation + reference: meeting to: meeting/structure_level_list_of_speakers_ids required: true restriction_mode: A @@ -3973,6 +4161,7 @@ tag: restriction_mode: A meeting_id: type: relation + reference: meeting to: meeting/tag_ids required: true restriction_mode: A @@ -4133,11 +4322,11 @@ theme: to: organization/theme_id type: relation organization_id: - required: true - restriction_mode: A - to: organization/theme_ids type: relation - constant: true + reference: organization + to: organization/theme_ids + restriction_mode: A + required: true topic: id: type: number @@ -4153,6 +4342,7 @@ topic: restriction_mode: A sequential_number: type: number + sequence_scope: meeting_id description: The (positive) serial number of this model in its meeting. This number is auto-generated and read-only. read_only: true @@ -4194,6 +4384,7 @@ topic: restriction_mode: A meeting_id: type: relation + reference: meeting to: meeting/topic_ids required: true restriction_mode: A @@ -4272,6 +4463,7 @@ user: type: relation to: gender/user_ids restriction_mode: A + reference: gender organization_management_level: type: string description: Hierarchical permission level for the whole organization. @@ -4289,7 +4481,17 @@ user: to: committee/user_ids restriction_mode: E read_only: true - description: Calculated field. + description: 'Calculated field: Returns committee_ids, where the user is manager + or member in a meeting' + sql: "(\n SELECT array_agg(DISTINCT ci.committee_id ORDER BY ci.committee_id)\n\ + \ FROM (\n -- Select committee_ids from meetings the user is part of\n \ + \ SELECT m.committee_id\n FROM meeting_user_t AS mu\n INNER JOIN meeting_t\ + \ AS m ON m.id = mu.meeting_id\n WHERE mu.user_id = u.id\n\n UNION\n\n\ + \ -- Select committee_ids from committee managers\n SELECT cmu.committee_id\n\ + \ FROM nm_committee_manager_ids_user_t cmu\n WHERE cmu.user_id = u.id\n\ + \n UNION\n\n -- Select home_committee_id from user\n SELECT u_hc.home_committee_id\n\ + \ FROM user_t u_hc\n WHERE u_hc.home_committee_id IS NOT NULL AND u_hc.id\ + \ = u.id\n ) AS ci\n) AS committee_ids\n" committee_management_ids: type: relation-list to: committee/manager_ids @@ -4306,23 +4508,28 @@ user: option_ids: type: relation-list to: option/content_object_id + reference: option restriction_mode: A vote_ids: type: relation-list to: vote/user_id + reference: vote restriction_mode: A delegated_vote_ids: type: relation-list to: vote/delegated_user_id + reference: vote restriction_mode: A poll_candidate_ids: type: relation-list to: poll_candidate/user_id + reference: poll_candidate restriction_mode: A home_committee_id: type: relation to: committee/native_user_ids restriction_mode: E + reference: committee history_position_ids: type: relation-list to: history_position/user_id @@ -4332,13 +4539,16 @@ user: to: history_entry/model_id restriction_mode: A meeting_ids: - type: number[] - description: Calculated. All ids from meetings calculated via meeting_user and - group_ids as integers. + type: relation-list + description: Calculated. All ids from meetings calculated via meeting_user. read_only: true + sql: "(\n SELECT array_agg(DISTINCT mu.meeting_id ORDER BY mu.meeting_id)\n \ + \ FROM meeting_user_t mu\n WHERE mu.user_id = u.id\n) AS meeting_ids\n" + to: meeting/user_ids restriction_mode: E organization_id: type: relation + reference: organization to: organization/user_ids required: true restriction_mode: F @@ -4365,6 +4575,7 @@ vote: option_id: type: relation to: option/vote_ids + reference: option equal_fields: meeting_id required: true restriction_mode: A @@ -4372,14 +4583,17 @@ vote: user_id: type: relation to: user/vote_ids + reference: user restriction_mode: A delegated_user_id: type: relation to: user/delegated_vote_ids + reference: user restriction_mode: A meeting_id: type: relation to: meeting/vote_ids + reference: meeting required: true restriction_mode: A constant: true diff --git a/dev/setup.cfg b/setup.cfg similarity index 68% rename from dev/setup.cfg rename to setup.cfg index 6575421d..9650f784 100644 --- a/dev/setup.cfg +++ b/setup.cfg @@ -17,3 +17,11 @@ extend-ignore = E203,E501 [mypy] disallow_untyped_defs = true + +[sqlfluff] +dialect = postgres +large_file_skip_byte_limit = 0 +processes = 0 + +[sqlfluff:rules:capitalisation.identifiers] +ignore_words = WARNING,