{"id":16193343,"url":"https://github.com/starnowski/posmulten","last_synced_at":"2025-10-29T17:11:36.380Z","repository":{"id":36354498,"uuid":"167456540","full_name":"starnowski/posmulten","owner":"starnowski","description":"Posmulten library is an open-source project for the generation of SQL DDL statements that make it easy for implementation of Shared Schema Multi-tenancy strategy via the Row Security Policies in the Postgres database.","archived":false,"fork":false,"pushed_at":"2024-11-19T16:30:25.000Z","size":3451,"stargazers_count":23,"open_issues_count":12,"forks_count":11,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-02-27T07:51:05.905Z","etag":null,"topics":["database","multi-tenancy","multi-tenant-applications","multi-tenant-database","postgresql","shared-schema"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"lgpl-2.1","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/starnowski.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"starnowski"}},"created_at":"2019-01-25T00:02:26.000Z","updated_at":"2025-01-06T14:37:51.000Z","dependencies_parsed_at":"2024-02-21T23:34:01.372Z","dependency_job_id":"016acf66-06c3-4290-ae37-7b0ffa150b28","html_url":"https://github.com/starnowski/posmulten","commit_stats":null,"previous_names":[],"tags_count":30,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/starnowski%2Fposmulten","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/starnowski%2Fposmulten/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/starnowski%2Fposmulten/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/starnowski%2Fposmulten/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/starnowski","download_url":"https://codeload.github.com/starnowski/posmulten/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243814907,"owners_count":20352037,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["database","multi-tenancy","multi-tenant-applications","multi-tenant-database","postgresql","shared-schema"],"created_at":"2024-10-10T08:14:36.592Z","updated_at":"2025-10-29T17:11:36.320Z","avatar_url":"https://github.com/starnowski.png","language":"Java","funding_links":["https://github.com/sponsors/starnowski"],"categories":[],"sub_categories":[],"readme":"# Posmulten\n\n[![Build Status](https://app.travis-ci.com/starnowski/posmulten.svg?branch=master)](https://app.travis-ci.com/starnowski/posmulten)\n[![Run tests for posmulten](https://github.com/starnowski/posmulten/actions/workflows/posmulten.yml/badge.svg)](https://github.com/starnowski/posmulten/actions/workflows/posmulten.yml)\n[![Maven Central](https://img.shields.io/maven-central/v/com.github.starnowski.posmulten/postgresql-core.svg?label=Maven%20Central)](https://search.maven.org/search?q=g:%22com.github.starnowski.posmulten%22%20AND%20a:%22postgresql-core%22)\n[![CodeQL](https://github.com/starnowski/posmulten/workflows/CodeQL/badge.svg)](https://github.com/starnowski/posmulten/actions?query=workflow%3ACodeQL)\n[![CodeQL for release branch](https://github.com/starnowski/posmulten/actions/workflows/codeql-release-analysis.yml/badge.svg)](https://github.com/starnowski/posmulten/actions/workflows/codeql-release-analysis.yml)\n\n\n* [Introduction](#introduction)\n* [Multi-tenancy](#multi-tenancy)\n    * [Separate database strategy](#separate-database)\n    * [Separate schema strategy](#separate-schema)\n    * [Shared schema strategy](#shared-schema)\n        * [Implementation requirements](#implementation-requirements)\n    * [How posmulten helps to implement shared schema strategy?](#how-posmulten-helps-to-implement-shared-schema-strategy)\n        * [Setting RLS policy](#setting-rls-policy)\n            * [Function that checks tenant access to a table row](#function-that-checks-tenant-access-to-a-table-row)\n            * [Function that checks if the passed identifier is the same as the current tenant identifier](#function-that-checks-if-the-passed-identifier-is-the-same-as-the-current-tenant-identifier)\n            * [Function that returns the current tenant identifier](#function-that-returns-the-current-tenant-identifier)\n            * [Function that set the current tenant identifier](#function-that-set-the-current-tenant-identifier)\n        * [Connecting to Database](#connecting-to-database)\n        * [Adding constraints for foreign key columns](#adding-constraints-for-foreign-key-columns)\n        * [Other columns modifications](#other-columns-modifications)\n* [How to start using posmulten](#how-to-start-using-posmulten)\n    * [Setting maven dependency](#setting-maven-dependency)\n    * [Building project locally](#building-project-locally)\n    * [How to start using builder](#how-to-start-using-builder)\n        * [Applying builder changes](#applying-builder-changes)\n        * [Dropping builder changes](#dropping-builder-changes)\n        * [Using posmulten components with database connection](#using-posmulten-components-with-database-connection)\n    * [Setting default database schema](#setting-default-database-schema)\n    * [Setting default database user for RLS policy](#setting-default-database-user-for-rls-policy)\n    * [Setting RLS Policy for table](#setting-rls-policy-for-table)\n        * [Setting RLS Policy for a table with a multi-column primary key](#setting-rls-policy-for-a-table-with-a-multi-column-primary-key)\n        * [Setting RLS Policy for a table without primary key](#setting-rls-policy-for-a-table-without-primary-key)\n    * [Force RLS Policy for table owner](#force-rls-policy-for-table-owner)\n    * [Adding a foreign key constraint](#adding-a-foreign-key-constraint)\n        * [Adding a foreign key constraint with a multi-column primary key](#adding-a-foreign-key-constraint-with-a-multi-column-primary-key)\n    * [Setting of type for tenant identifier value](#setting-of-type-for-tenant-identifier-value)\n    * [Setting the property name that stores tenant identifier value](#setting-the-property-name-that-stores-tenant-identifier-value)\n    * [Adding default value for tenant column](#adding-default-value-for-tenant-column)\n        * [Skipping adding default value for tenant column for a single table](#skipping-adding-default-value-for-tenant-column-for-a-single-table)\n    * [Setting default tenant column name](#setting-default-tenant-column-name)\n    * [Setting function name that returns the current tenant identifier](#setting-function-name-that-returns-the-current-tenant-identifier)\n    * [Setting function name that sets the current tenant identifier](#setting-function-name-that-sets-the-current-tenant-identifier)\n    * [Setting function name that checks if current tenant has authorities to a table row](#setting-function-name-that-checks-if-current-tenant-has-authorities-to-a-table-row)\n    * [Setting function name that checks if passed identifier is the same as current tenant identifier](#setting-function-name-that-checks-if-passed-identifier-is-the-same-as-current-tenant-identifier)\n    * [Setting function name that checks if passed primary key for a specific table exists for the current tenant](#setting-function-name-that-checks-if-passed-primary-key-for-a-specific-table-exists-for-the-current-tenant)\n    * [Setting a list of invalid tenant identifier values](#setting-a-list-of-invalid-tenant-identifier-values)\n    * [Setting custom name for table tenant column constraint](#setting-custom-name-for-table-tenant-column-constraint)\n    * [Setting foreign key constraint where tenant column is part of composite key](#setting-foreign-key-constraint-where-tenant-column-is-part-of-composite-key)\n    * [Naming convention and its constraints](#naming-convention-and-its-constraints)\n* [Adding custom sql definitions](#adding-custom-sql-definitions)\n* [Using template variables in context builder](#using-template-variables-in-context-builder)\n* [GUI - Swing application](#gui)\n* [Reporting issues](#reporting-issues)\n* [Project contribution](#project-contribution)\n\n\n# Introduction\nPosmulten library is an open-source project for the generation of SQL DDL statements that make it easy to implementation of the [shared schema multi-tenancy strategy](#shared-schema) via the [row security policies](https://www.postgresql.org/docs/9.6/ddl-rowsecurity.html) in the Postgres database.\nProject is tested for compatibility with the Postgres database in versions 9.6, 10.14, 11.9, 12.4, and 13.0.\nThe library is written in a java programming language.\nThe project required at least java 8.\n\n# Multi-tenancy\n\nBased on [hibernate documentation](https://docs.jboss.org/hibernate/orm/4.3/devguide/en-US/html/ch16.html) \n_`the term multi-tenancy in general is applied to software development to indicate an architecture in which a single running instance of an application simultaneously serves multiple clients (tenants).`_\nThere are three main strategies [separate database](#separate-database), [separate schema](#separate-schema) and [shared schema](#shared-schema)\nBelow you can find short description of those strategies. Of course we will focus more on [shared schema](#shared-schema).\nFor more information about what pros and cons of each approach please check below links:\n\n* https://docs.jboss.org/hibernate/orm/4.3/devguide/en-US/html/ch16.html\n* https://medium.com/@MentorMate/increase-efficiency-with-multi-tenant-cloud-software-architecture-4261fca6025e\n\n## Separate database\nIn this strategy each tenant's data is stored in separate database. \nFor this obvious reason this approach gives the highest isolation level.\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://raw.githubusercontent.com/starnowski/posmulten/master/doc/Separate_database.png\"\u003e\n\u003c/p\u003e\n\n\n## Separate schema\nStrategy assumes that each tenant's data is kept in his own schema but all those schemas exists in single database instance.\nObviously this approach offers lower isolation level than [separate database](#separate-database) but allows to potentially save costs for infrastructure by not having multiple database instances.\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://raw.githubusercontent.com/starnowski/posmulten/master/doc/Separate_schema.png\"\u003e\n\u003c/p\u003e\n\n## Shared schema\nIn this strategies data all tenants are kept in single database and same schema.\nAlthough there is no limitation that there has to be only one schema in database but all tenants should have same access to them.\nThe strategy assumes that all tables in a database (with an exception for tables that stores vocabulary data or data available for all tenants) have a column that stores tenant identifier.\nOf course, based on this value column, we know which tenant is the owner of the table raw.\nObviously, this approach offers a lower isolation level than both previous strategies but, just like [shared schema](#shared-schema) strategy, allows to save costs for infrastructure potentially.\nIn comparison to [shared schema](#shared-schema) strategy, we can say that although Postgres can handle multiple schemas and even we can find comments from persons who use this strategy.\nWe can find statements that with larger number of tenants the maintenance for this approach is harder than when schema is shared between tenants.\nExecuting ddl scripts for hundreds schemas might be easily automated but still it might create complexity and slower deployment.\nIt does not mean that executing ddl scripts for database with shared schema approach is error free.\nEvery change should be considered.\nThere are some other [important considerations](#implementation-requirements) which should taken before deciding for shared schema strategy.\n\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://raw.githubusercontent.com/starnowski/posmulten/master/doc/Shared_schema.png\"\u003e\n\u003c/p\u003e\n\n### Implementation requirements\nJust like it mention in section above, the [shared schema](#shared-schema) strategy assumes that all tables shared by tenant have column which defines its owner.\nThis means that the value of this column has to be checked in each SQL query and operation.\nColumn has to checked during execution of SELECT statement and all operations that modifies data like UPDATE, INSERT and DELETE.\nIn case when decision is made that application should handle this checks then every sql statement send to database by application need to contains in WHERE statement condition for tenant column for each table.\nIt might be easier if application use some ORM framework. \nBut still if application code contains any custom query then developer has to be aware that he needs to add checks for tenant identifier column.\nThe pros for this approach is that if we would ever consider changing database engine for application then it would be quick easy\nHowever there are cons for such situation if for example we would consider of having many application that use same database.\nAll application should have implemented tenant column checks. \nNot to mention if those application would be writen in different programing languages.\n\nAnother approach is that the database engine is going to handle tenant column checks.\nOf course, not all database engines support such features.\nOne such database is Oracle with a feature called the [virtual private database](https://www.oracle.com/database/technologies/virtual-private-db.html).\nAnd also Postgres has a similar feature called the [row security policy](https://www.postgresql.org/docs/9.6/ddl-rowsecurity.html) which posmulten is [using](#setting-rls-policy).\nThis approach might be better if you planned to have multiple different applications connected to your database.\nBesides some connection adjustments, there is no additional logic that has to be added to every SQL statement created by the application code.\nNot to mention the situation when connected applications are written in different programming languages.\n\nOne thing that might be considered during implementation is constraints that check if the foreign key columns reference rows that belong to the same tenant.\nOf course, assuming if the above requirement is fulfilled, then even if SQL injection will succeed, the application that checks tenant column should not display the record for other tenants or modify it.\nBut there might be a situation when a separate application is also connected to a database but operate on data without checking the tenant column.\nFor example, statistical data gathering or other specific goals where joining data from different tables is crucial.\nPosmulten helps to [create such constraints](#adding-constraints-for-foreign-key-columns) for the foreign key columns.\n\n\n## How posmulten helps to implement shared schema strategy?\n\nBelow there is short explanation how posmulten helps to implement shared schema strategy and what ddl statements are generated.\nThe ddl statements examples are generated for tables users and posts.\n\n```sql\nCREATE TABLE public.users\n(\n    id bigint NOT NULL,\n    name character varying(255),\n    tenant_id character varying(255),\n    CONSTRAINT users_pkey PRIMARY KEY (id)\n)\nWITH (\n    OIDS = FALSE\n)\nTABLESPACE pg_default;\n\nCREATE TABLE public.posts\n(\n    id bigint NOT NULL,\n    text text NOT NULL,\n    user_id bigint NOT NULL,\n    tenant_id character varying(255),\n    CONSTRAINT fk_posts_user_id FOREIGN KEY (user_id)\n              REFERENCES users (id) MATCH SIMPLE,\n    CONSTRAINT posts_pkey PRIMARY KEY (id)\n)\nWITH (\n    OIDS = FALSE\n)\nTABLESPACE pg_default;\n```\n\n### Setting RLS policy\n\nPosmulten use [row security policy](https://www.postgresql.org/docs/9.6/ddl-rowsecurity.html) mechanism to handle tenant data isolation.\nCreates policy for all operations (syntax \"ALL\" applies to SQL commands like INSERT, SELECT, UPDATE, DELETE) and [specific user](#setting-default-database-user-for-rls-policy).\n\u003cbr/\u003e\n```sql\nCREATE POLICY posts_table_rls_policy ON posts\nFOR ALL\nTO \"postgresql-core-owner\"\nUSING (tenant_has_authorities(tenant_id, 'ALL', 'USING', 'posts', 'public'))\nWITH CHECK (tenant_has_authorities(tenant_id, 'ALL', 'WITH_CHECK', 'posts', 'public'));\n```\n\u003cbr/\u003e\n\nA statement that enables the [row security policy](https://www.postgresql.org/docs/9.6/ddl-rowsecurity.html) mechanism is also created.\n\u003cbr/\u003e\n```sql\nALTER TABLE \"posts\" ENABLE ROW LEVEL SECURITY;\n```\n\u003cb\u003eIMPORTANT!\u003c/b\u003e\n\u003cbr/\u003e\nWhen the database user for who row security policy is supposed to be created is table owner, it is required to [force row security policy](#force-rls-policy-for-table-owner) mechanism.\nBy default, posmulten does not force this mechanism.\n\u003cbr/\u003e\n\u003cbr/\u003e\n\n#### Function that checks tenant access to a table row\nCreates a DDL statement for a function that checks if the current tenant for the database session has access to table row based on tenant column (for the case below it is \"tenant_id\") value.\n***Current function logic is not complex, but this might be changed in the next release.***\nThe [function name](#setting-function-name-that-checks-if-current-tenant-has-authorities-to-a-table-row) can be customize.\nThe type of function first argument is the same as the default type for tenant identifier value which can be [customized](#setting-of-type-for-tenant-identifier-value).\n\u003cbr/\u003e\n\n```sql\nCREATE OR REPLACE FUNCTION tenant_has_authorities(VARCHAR(255), VARCHAR(255), VARCHAR(255), VARCHAR(255), VARCHAR(255)) RETURNS BOOLEAN AS $$\nSELECT is_id_equals_current_tenant_id($1)\n$$ LANGUAGE sql\nSTABLE\nPARALLEL SAFE;\n```\n\u003cbr/\u003e\n\n#### Function that checks if the passed identifier is the same as the current tenant identifier\nCreates a DDL statement for a function that checks if the current tenant identifier set for the database session is equal to the passed tenant identifier.\nThe [function name](#setting-function-name-that-checks-if-passed-identifier-is-the-same-as-current-tenant-identifier) and function [argument type](#setting-of-type-for-tenant-identifier-value) can be changed.\n\u003cbr/\u003e\n\n```sql\nCREATE OR REPLACE FUNCTION is_id_equals_current_tenant_id(VARCHAR(255)) RETURNS BOOLEAN AS $$\nSELECT $1 = get_current_tenant_id()\n$$ LANGUAGE sql\nSTABLE\nPARALLEL SAFE;\n```\n\u003cbr/\u003e\n\n#### Function that returns the current tenant identifier\nNext function that is created by Posmulten project is function that returns value of identifier for current tenant.\nFunction reads property value that is save for database session. \nThe [property name](#setting-the-property-name-that-stores-tenant-identifier-value) and [function name](#setting-function-name-that-returns-the-current-tenant-identifier) can customized.\n\u003cbr/\u003e\n\n```sql\nCREATE OR REPLACE FUNCTION get_current_tenant_id() RETURNS VARCHAR(255) AS $$\nSELECT current_setting('c.c_ten')\n$$ LANGUAGE sql\nSTABLE\nPARALLEL SAFE;\n```\n\u003cbr/\u003e\n\n#### Function that set the current tenant identifier\nAnother crucial function is that one which sets current tenant identifier in database connection.\nThe [property name](#setting-the-property-name-that-stores-tenant-identifier-value) and [function name](#setting-function-name-that-sets-the-current-tenant-identifier) can customized.\nThe type of function argument is the same as the default type for tenant identifier value which can be [customized](#setting-of-type-for-tenant-identifier-value).\n\u003cbr/\u003e\n\n```sql\nCREATE OR REPLACE FUNCTION set_current_tenant_id(VARCHAR(255)) RETURNS VOID AS $$\nBEGIN\nPERFORM set_config('c.c_ten', $1, false);\nEND\n$$ LANGUAGE plpgsql\nVOLATILE;\n```\n\u003cbr/\u003e\n\n### Connecting to Database \nAfter correct setup of RLS policies the way how we connect to database and execute sql script has to be changed a little bit.\nFor example let's assume that the name of session property that stores current tenant identifier is \"c.c_ten\".\nThe function that set value for this property is set_current_tenant_id(VARCHAR(255)) and function that return its value is called get_current_tenant_id().\nIf opening connection as user for which the RLS was configured it is required to set tenant identifier.\nBelow there is example that show what happens in situation when tenant identifier is not set.\n\u003cb\u003eImportant\u003c/b\u003e, all examples were generated in the Postgres database in version 9.6. \n```sql\nSELECT * FROM users;\n```\nFor displaying rows from the users table without setting tenant identifier there is going to be thrown exception.\n```sql\nERROR:  unrecognized configuration parameter \"c.c_ten\"\nSQL state: 42704\n```\n\nLet's pretend that our newly created tenant should have the name \"SOME_TENANT_1\".\n```sql\nSELECT set_current_tenant_id('SOME_TENANT_1');\nSELECT COUNT(*) FROM users;\n```\n\nIn the beginning, there are no records.\nBelow there is an example how to insert into table for tenant.\n```sql\nSELECT set_current_tenant_id('SOME_TENANT_1');\nINSERT INTO users (id, name) VALUES (1, 'Szymon Tarnowski');\nINSERT INTO users (id, name, tenant_id) VALUES (2, 'John Doe', 'SOME_TENANT_1');\n```\n\u003cb\u003eIMPORTANT!\u003c/b\u003eThe first insert statement without tenant_id is possible because it was used a statement that [adds default value](#adding-default-value-for-tenant-column).\nAfter inserting above records, the previous select statements should return result 2.\nIn case if we want to change current tenant to 'TENANT_X_2' and display all rows we should get zero results. \n```sql\nSELECT set_current_tenant_id('TENANT_X_2');\nSELECT COUNT(*) FROM users;\n```\nAfter adding a single record we should get the one as a select query result.\n```sql\nSELECT set_current_tenant_id('TENANT_X_2');\nINSERT INTO users (id, name) VALUES (3, 'Jimmy Doe');\nSELECT COUNT(*) FROM users;\n```\nAfter deleting all results from the users table, the select query should return zero results.\n```sql\nSELECT set_current_tenant_id('TENANT_X_2');\nDELETE FROM users;\nSELECT COUNT(*) FROM users;\n```  \n\nBut if we change current tenant to the 'SOME_TENANT_1' then for select query we should get two records.\n```sql\nSELECT set_current_tenant_id('SOME_TENANT_1');\nSELECT COUNT(*) FROM users;\n```\n\n### Adding constraints for foreign key columns\nThe library has the possibility to add a [foreign key constraint](#adding-a-foreign-key-constraint) that checks if foreign key value references to the row which belongs to the current tenant.\n\u003cbr/\u003e\n\n```sql\nALTER TABLE \"posts\" ADD CONSTRAINT posts_users_fk_cu CHECK ((user_id IS NULL) OR (is_user_belongs_to_current_tenant(user_id)));\n```\n\u003cbr/\u003e\n\nThe library also creates the function that checks if the passed identifier exists in a specific table.\nThe statement is created only when the is a request for the creation of a [foreign key constraint](#adding-a-foreign-key-constraint).\nThe types of arguments for this function are based on column types that a part of the primary key [declared for the table](#setting-rls-policy-for-table) which foreign key references to.\n\u003cbr/\u003e\n\n```sql\nCREATE OR REPLACE FUNCTION is_user_belongs_to_current_tenant(bigint) RETURNS BOOLEAN AS $$\nSELECT EXISTS (\n\tSELECT 1 FROM users rt WHERE rt.id = $1 AND rt.tenant_id = get_current_tenant_id()\n)\n$$ LANGUAGE sql\nSTABLE\nPARALLEL SAFE;\n```\n\u003cbr/\u003e\n\n### Other columns modifications\nThere are also operations generated by the library that alters table columns.\nFor example, the library can add a statement that [adds default value](#adding-default-value-for-tenant-column) for the tenant identifier column or [add a tenant column](#adding-tenant-column-to-tenant-table) to the table.\n\u003cbr/\u003e\n\n# How to start using posmulten\nPosmulten is a java project, so besides other projects written in java, you can also use the project as a dependency on projects written in languages executed on java virtual machine.\nThe project required at least java 8. \n\n\u003cb\u003eIMPORTANT!\u003c/b\u003e\n\u003cbr/\u003e\nThere is also [posmulten-ddl wrapper script](https://github.com/starnowski/posmulten-ddl) that gives the possibility of using the Posmulten library outside the java project.\n\n### Setting maven dependency\nThe project is available in the central maven repository.\nYou can use it just by adding it as a dependency in the project descriptor file (pom.xml).\n\n```xml\n        \u003cdependency\u003e\n            \u003cgroupId\u003ecom.github.starnowski.posmulten\u003c/groupId\u003e\n            \u003cartifactId\u003epostgresql-core\u003c/artifactId\u003e\n            \u003cversion\u003e0.9.0\u003c/version\u003e\n        \u003c/dependency\u003e\n```\n\n### Building project locally\nIf someone would like to build the project locally from the source please see the CONTRIBUTING.md file to check how to set up the project locally.\n\n### How to start using builder\nThe library's main public component is DefaultSharedSchemaContextBuilder, which produces all required DDL statements based on passed criteria.\nFor example:\n\n```java\nimport com.github.starnowski.posmulten.postgresql.core.context.ISharedSchemaContext;\nimport com.github.starnowski.posmulten.postgresql.core.context.DefaultSharedSchemaContextBuilder;\n//...\n    Map\u003cString, String\u003e usersTablePrimaryKeyNameToType = new HashMap();\n    usersTablePrimaryKeyNameToType.put(\"id\", \"bigint\");\n    Map\u003cString, String\u003e postsTablePrimaryKeyNameToType = new HashMap();\n    postsTablePrimaryKeyNameToType.put(\"id\", \"bigint\");\n    DefaultSharedSchemaContextBuilder defaultSharedSchemaContextBuilder = new DefaultSharedSchemaContextBuilder(null);\n    defaultSharedSchemaContextBuilder.setGrantee(\"db_user\");\n    defaultSharedSchemaContextBuilder.createRLSPolicyForTable(\"users\", usersTablePrimaryKeyNameToType, \"tenant_id\", \"users_table_rls_policy\");\n    defaultSharedSchemaContextBuilder.createRLSPolicyForTable(\"posts\", postsTablePrimaryKeyNameToType, \"tenant_id\", \"posts_table_rls_policy\");\n    //... other criteria\n    ISharedSchemaContext sharedSchemaContext = defaultSharedSchemaContextBuilder.build();\n```\n\nThe builder component, as a result of method build(), returns an object of type ISharedSchemaContext.\nThe type contains all properties required to create a shared schema strategy and components that help using it correctly in your java code.\n\n#### Applying builder changes\nOne of the crucial methods of the ISharedSchemaContext interface is \"getSqlDefinitions()\".\nIt returns list of object of type \"SQLDefinition\" that contains method getCreateScript().\nThe method returns DDL statement that should be applied to implement shared schema strategy.\n\u003cbr/\u003e\n\u003cb\u003eIMPORTANT!\u003c/b\u003e\n\u003cbr/\u003e\nThe list's order is crucial because the DDL statements returned by the getCreateScript() method that represents each object of the list should be applied based on the list's order.\nJust like in code example below:\n\u003cbr/\u003e\n\n\n```java\nimport com.github.starnowski.posmulten.postgresql.core.common.SQLDefinition;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.jdbc.core.JdbcTemplate;\n//...\n        @Autowired\n        JdbcTemplate jdbcTemplate;\n//...\n        List\u003cSQLDefinition\u003e sqlDefinitions = sharedSchemaContext.getSqlDefinitions();\n        sqlDefinitions.forEach(sqlDefinition -\u003e\n        {\n            jdbcTemplate.execute(sqlDefinition.getCreateScript());\n        });\n```\n\n#### Dropping builder changes\nThe second important method of the SQLDefinition type is getDropScript().\nIt returns a statement that drops changes applied by the statement returned by the getCreateScript() method.\n\u003cbr/\u003e\n\u003cb\u003eIMPORTANT!\u003c/b\u003e\n\u003cbr/\u003e\nBy default, there is no assumption that statement has to contains the compensation operation for operation returned by the getCreateScript() method.\nThis means that the operation can not be by default treated as a rollback operation, but an operation that removes changes applied by statement returned by the getCreateScript() method.\n\n\u003cbr/\u003e\n\u003cb\u003eIMPORTANT!\u003c/b\u003e\n\u003cbr/\u003e\nJust like was mentioned in the previous section that the statements returned by the getCreateScript() method for objects from the list returned from the getSqlDefinitions() method, should be applied based on the list's order.\nBased on that fact, the statements returned by the getDropScript() method for objects, should be executed in the reverse list's order. \n\n```java\nimport com.github.starnowski.posmulten.postgresql.core.common.SQLDefinition;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.jdbc.core.JdbcTemplate;\n//...\n        @Autowired\n        JdbcTemplate jdbcTemplate;\n//...\n        List\u003cSQLDefinition\u003e sqlDefinitions = sharedSchemaContext.getSqlDefinitions();\n        //Run sql statements in reverse order\n        LinkedList\u003cSQLDefinition\u003e stack = new LinkedList\u003c\u003e();\n        sqlDefinitions.forEach(stack::push);\n        stack.forEach(sqlDefinition -\u003e\n        {\n            jdbcTemplate.execute(sqlDefinition.getDropScript());\n        });\n```\n\n#### Using posmulten components with database connection\nOther useful components that type ISharedSchemaContext contains is object of type \"ISetCurrentTenantIdFunctionPreparedStatementInvocationFactory\" returned by method getISetCurrentTenantIdFunctionPreparedStatementInvocationFactory().\nComponent of type ISetCurrentTenantIdFunctionPreparedStatementInvocationFactory returns statement that sets current tenant identifier and can be used by PreparedStatement object.\n\n```java\nimport com.github.starnowski.posmulten.postgresql.core.rls.function.ISetCurrentTenantIdFunctionPreparedStatementInvocationFactory;\nimport java.sql.PreparedStatement;\nimport java.sql.ResultSet;\n//...\n        @Autowired\n        JdbcTemplate jdbcTemplate;\n//...\n        ISetCurrentTenantIdFunctionPreparedStatementInvocationFactory setCurrentTenantIdFunctionPreparedStatementInvocationFactory = sharedSchemaContext.getISetCurrentTenantIdFunctionPreparedStatementInvocationFactory();\n        jdbcTemplate.execute(setCurrentTenantIdFunctionPreparedStatementInvocationFactory.returnPreparedStatementThatSetCurrentTenant(), (PreparedStatementCallback\u003cInteger\u003e) preparedStatement -\u003e {\n            preparedStatement.setString(1, \"some-tenant-id-SDFAFD-DZXCV\");\n            preparedStatement.executeQuery();\n            ResultSet rs = preparedStatement.getConnection().createStatement().executeQuery(\"SELECT COUNT(*) FROM users\");\n            rs.next();\n            return rs.getInt(1);\n        });\n```\n\nAssuming that function that [sets current tenant](#setting-function-name-that-sets-the-current-tenant-identifier) has name 'set_tenant' the ISetCurrentTenantIdFunctionPreparedStatementInvocationFactory#returnPreparedStatementThatSetCurrentTenant() method is going to return string just like below:\n```sql\nSELECT set_tenant(?);\n```\n\nType ISharedSchemaContext contains also object of type ISetCurrentTenantIdFunctionInvocationFactory returned by method getISetCurrentTenantIdFunctionInvocationFactory().\nThe ISetCurrentTenantIdFunctionInvocationFactory type has method that returns similar result as ISetCurrentTenantIdFunctionPreparedStatementInvocationFactory but without \"?\" mark.\nInstead it sets specific value passed as argument, for example:\n\n```java\n    String tenant = \"TXDS-tenant-id\";\n    ISetCurrentTenantIdFunctionInvocationFactory setCurrentTenantIdFunctionDefinition = sharedSchemaContext.getISetCurrentTenantIdFunctionInvocationFactory();\n    jdbcTemplate.execute(String.format(\"%1$s UPDATE users %2$s SET name = '%2$s' WHERE id = %3$d ;\", setCurrentTenantIdFunctionDefinition.generateStatementThatSetTenant(tenant), updatedName, user.getId()));\n```\n\u003cbr/\u003e\n\u003cb\u003eIMPORTANT!\u003c/b\u003e\n\u003cbr/\u003e\n\nPlease in mind that if it is required to use custom sql statement that might use values passed out side application, for instance from application user then is more secure to use PreparedStatement type.\nIt is also worth mentioning that some frameworks for optimization purposes use database connection pools.\nJust like in the examples above where the JdbcTemplate is used.\nIn case when the component is used in the context of the already existed transaction, then the Spring framework might use a connection that is kept in context for the local thread. \n\n\n### Setting default database schema\nBuilder component has one constructor with one String parameter (there is a task to add non-argument constructor [135](https://github.com/starnowski/posmulten/issues/135)).\nThe constructor is name of default schema for which statement are going to be created.\nIf null is going to passed then by default the schema name is not going to be added to created DDL statements.\nFor example the code below:\n```java\nimport com.github.starnowski.posmulten.postgresql.core.context.DefaultSharedSchemaContextBuilder;\n//...\n    DefaultSharedSchemaContextBuilder defaultSharedSchemaContextBuilder = new DefaultSharedSchemaContextBuilder(null); // Constructor with passed null parameter\n    Map\u003cString, String\u003e usersTablePrimaryKeyNameToType = new HashMap();\n    usersTablePrimaryKeyNameToType.put(\"id\", \"bigint\");\n    Map\u003cString, String\u003e postsTablePrimaryKeyNameToType = new HashMap();\n    postsTablePrimaryKeyNameToType.put(\"id\", \"bigint\");\n    Map\u003cString, String\u003e postsTableForeignKeyToUserPrimaryKey = new HashMap();\n    postsTableForeignKeyToUserPrimaryKey.put(\"user_id\", \"id\");\n    defaultSharedSchemaContextBuilder.setGrantee(\"postgresql-core-owner\");\n    defaultSharedSchemaContextBuilder.createRLSPolicyForTable(\"users\", usersTablePrimaryKeyNameToType, \"tenant_id\", \"users_table_rls_policy\");\n    defaultSharedSchemaContextBuilder.createRLSPolicyForTable(\"posts\", postsTablePrimaryKeyNameToType, \"tenant_id\", \"posts_table_rls_policy\");\n    //...\n    defaultSharedSchemaContextBuilder.createSameTenantConstraintForForeignKey(\"posts\", \"users, postsTableForeignKeyToUserPrimaryKey, \"posts_users_fk_cu\");\n```\n\nis going to produce below statements (displayed only few results)\n\n```sql\nCREATE POLICY users_table_rls_policy ON users\nFOR ALL\nTO \"postgresql-core-owner\"\nUSING (tenant_has_authorities(tenant_id, 'ALL', 'USING', 'users', 'public'))\nWITH CHECK (tenant_has_authorities(tenant_id, 'ALL', 'WITH_CHECK', 'users', 'public'));\n\n---..\nALTER TABLE \"posts\" ADD CONSTRAINT posts_users_fk_cu CHECK ((user_id IS NULL) OR (is_user_belongs_to_current_tenant(user_id)));\n```\n\nIn case if the \"non_public_schema\" value would be passed to constructor the result would look different.\n\n```java\n//...\n    DefaultSharedSchemaContextBuilder defaultSharedSchemaContextBuilder = new DefaultSharedSchemaContextBuilder(\"non_public_schema\"); // Constructor with passed \"non_public_schema\" parameter\n//...\n```\n\nIt would contain the \"non_public_schema\" schema name.\n\n```sql\nCREATE POLICY users_table_rls_policy ON non_public_schema.users\nFOR ALL\nTO \"postgresql-core-owner\"\nUSING (non_public_schema.tenant_has_authorities(tenant_id, 'ALL', 'USING', 'users', 'non_public_schema'))\nWITH CHECK (non_public_schema.tenant_has_authorities(tenant_id, 'ALL', 'WITH_CHECK', 'users', 'non_public_schema'));\n\n---..\nALTER TABLE \"non_public_schema\".\"posts\" ADD CONSTRAINT posts_users_fk_cu CHECK ((user_id IS NULL) OR (non_public_schema.is_user_belongs_to_current_tenant(user_id)));\n```\n\n### Setting default database user for RLS policy\nBuilder required to specify default database user for which the [row security policies](https://www.postgresql.org/docs/9.6/ddl-rowsecurity.html) are going to be created.\nFor the below criteria:\n```java\nimport com.github.starnowski.posmulten.postgresql.core.context.DefaultSharedSchemaContextBuilder;\n//...\n    DefaultSharedSchemaContextBuilder defaultSharedSchemaContextBuilder = new DefaultSharedSchemaContextBuilder(null); // Constructor with passed null parameter\n    Map\u003cString, String\u003e usersTablePrimaryKeyNameToType = new HashMap();\n    usersTablePrimaryKeyNameToType.put(\"id\", \"bigint\");\n    defaultSharedSchemaContextBuilder.createRLSPolicyForTable(\"users\", usersTablePrimaryKeyNameToType, \"tenant_id\", \"users_table_rls_policy\");\n    //...\n```\nand database user with name \"postgresql-core-owner\"\n```java\n    //...\n    defaultSharedSchemaContextBuilder.setGrantee(\"postgresql-core-owner\");\n    //...\n```\nthe builder will produce:\n```sql\nCREATE POLICY users_table_rls_policy ON users\nFOR ALL\nTO \"postgresql-core-owner\"\nUSING (tenant_has_authorities(tenant_id, 'ALL', 'USING', 'users', 'public'))\nWITH CHECK (tenant_has_authorities(tenant_id, 'ALL', 'WITH_CHECK', 'users', 'public'));\n```\nFor user \"app-first-user\"\n```java\n    //...\n    defaultSharedSchemaContextBuilder.setGrantee(\"app-first-user\");\n    //...\n```\nthe builder will produce:\n```sql\nCREATE POLICY users_table_rls_policy ON users\nFOR ALL\nTO \"app-first-user\"\nUSING (tenant_has_authorities(tenant_id, 'ALL', 'USING', 'users', 'public'))\nWITH CHECK (tenant_has_authorities(tenant_id, 'ALL', 'WITH_CHECK', 'users', 'public'));\n```\n\n### Setting RLS Policy for table\nThe most crucial thing from builder perspective is to define which tables need have created [row security policy](https://www.postgresql.org/docs/9.6/ddl-rowsecurity.html).\nThe RLS policy is added via method:\n```javadoc\ncom.github.starnowski.posmulten.postgresql.core.context.DefaultSharedSchemaContextBuilder#createRLSPolicyForTable(String table, Map\u003cString, String\u003e primaryKeyColumnsList, String tenantColumnName, String rlsPolicyName)\n```\n\u003cb\u003etable\u003c/b\u003e - \u003cb\u003e(Required)\u003c/b\u003e table name.\u003cbr/\u003e\n\u003cb\u003eprimaryKeyColumnsList\u003c/b\u003e - \u003cb\u003e(Required)\u003c/b\u003e map of primary key columns and their types in the table. The column name is the map key and the column type is its value.\u003cbr/\u003e\n\u003cb\u003etenantColumnName\u003c/b\u003e - \u003cb\u003e(Optional)\u003c/b\u003e name of the column that stores tenant identifier in table. In null value will be passed then default tenant column name will be used.\nSet by builder or [custom value](#setting-default-tenant-column-name).\u003cbr/\u003e\n\u003cb\u003erlsPolicyName\u003c/b\u003e - \u003cb\u003e(Required)\u003c/b\u003e name of row level security policy (see [task 48](https://github.com/starnowski/posmulten/issues/48)).\u003cbr/\u003e\n\nFor the below criteria:\n```java\nimport com.github.starnowski.posmulten.postgresql.core.context.DefaultSharedSchemaContextBuilder;\n//...\n    DefaultSharedSchemaContextBuilder defaultSharedSchemaContextBuilder = new DefaultSharedSchemaContextBuilder(\"some_schema\");\n    defaultSharedSchemaContextBuilder.setGrantee(\"db-us\");\n    //...\n```\nand database policy declaration for table users_tab:\n```java\n    //...\n    usersTablePrimaryKeyNameToType.put(\"user_uuid\", \"UUID\");\n    defaultSharedSchemaContextBuilder.createRLSPolicyForTable(\"users_tab\", usersTablePrimaryKeyNameToType, \"ten_col\", \"users_policy\");\n    //...\n```\nthe builder will produce:\n```sql\nCREATE POLICY users_policy ON some_schema.users_tab\nFOR ALL\nTO \"db-us\"\nUSING (some_schema.tenant_has_authorities(ten_col, 'ALL', 'USING', 'users_tab', 'some_schema'))\nWITH CHECK (some_schema.tenant_has_authorities(ten_col, 'ALL', 'WITH_CHECK', 'users_tab', 'some_schema'));\n```\n\n#### Setting RLS Policy for a table with a multi-column primary key\nIn case when the table has a more complex primary key than a single column. \nAll columns that are part of the primary key have to be passed to the method. \nFor example, for below comments table with two-column primary:\n```sql\nCREATE TABLE public.comments\n(\nid int NOT NULL,\nuser_id bigint NOT NULL,\ntext text NOT NULL,\ntenant character varying(255),\n\nparent_comment_id int,\nparent_comment_user_id bigint,\n\nCONSTRAINT fk_comments_users_id FOREIGN KEY (user_id)\nREFERENCES public.users (id) MATCH SIMPLE,\n\nCONSTRAINT fk_comments_parent_id FOREIGN KEY (parent_comment_id, parent_comment_user_id)\nREFERENCES public.comments (id, user_id) MATCH SIMPLE,\n\nCONSTRAINT comments_pkey PRIMARY KEY (id, user_id)\n```\n\nthe declaration should like below:\n```java\n    //...\n    Map\u003cString, String\u003e commentsTablePrimaryKeyNameToType = new HashMap();\n    commentsTablePrimaryKeyNameToType.put(\"id\", \"int\");\n    commentsTablePrimaryKeyNameToType.put(\"user_id\", \"bigint\");\n    defaultSharedSchemaContextBuilder.createRLSPolicyForTable(\"comments\", commentsTablePrimaryKeyNameToType, \"tenant\", \"comments_table_rls_policy\");\n    //...\n```\n\n\u003cb\u003eIMPORTANT!\u003c/b\u003e\u003cbr/\u003e\nThe primary key columns map is not required to create RLS policy, but it is required when there is any relation between tables (or a single table has a relation to itself) where there is a foreign key constraint. \nIn such a case, posmulten needs to know the primary key table definition to create the correct function that checks if a table row exists for a specific tenant.\nSuch a function is created only if there is any [foreign key constraint declaration](#adding-a-foreign-key-constraint). \n\n#### Setting RLS Policy for a table without primary key\nIn case when RLS policy has to be created for a table without a primary key, for example, for join table in many to many relations.\nThe empty map has to be passed as a method argument.\n\nFor example, for the below SQL table:\n\n```sql\nCREATE TABLE public.users_groups\n(\n    user_id bigint NOT NULL,\n    group_id uuid NOT NULL,\n    tenant_id character varying(255),\n    CONSTRAINT fk_users_groups_user_id FOREIGN KEY (user_id)\n        REFERENCES users (id) MATCH SIMPLE,\n    CONSTRAINT fk_users_groups_group_id FOREIGN KEY (group_id)\n            REFERENCES groups (uuid) MATCH SIMPLE\n)\nWITH (\n    OIDS = FALSE\n)\nTABLESPACE pg_default;\n```\nthe RLS policy declaration should look like this:\n```java\n    //...\n    defaultSharedSchemaContextBuilder.createRLSPolicyForTable(\"users_groups\", new HashMap\u003c\u003e(), \"tenant_id\", \"users_groups_table_rls_policy\");\n    //...\n```\n\u003cb\u003eINFORMATION!\u003c/b\u003e There is a [task](https://github.com/starnowski/posmulten/issues/138) whose goal is to add the ability to pass a null value as a map to give the same result.\n\n### Force RLS Policy for table owner\nIn situation when RLS policy has to be created for database user that is a tables owner in schema then there has to additional DDL instruction created for each table.\nIt is because just like is mentioned in Postgres documentation \n_Table owners normally bypass row security as well, though a table owner can choose to be subject to row security with ALTER TABLE ... FORCE ROW LEVEL SECURITY._\nTo specify this option builder has method:\n```javadoc\ncom.github.starnowski.posmulten.postgresql.core.context.DefaultSharedSchemaContextBuilder#setForceRowLevelSecurityForTableOwner(boolean forceRowLevelSecurityForTableOwner)\n```\n\nFor example, for below requirements:\n```java\nimport com.github.starnowski.posmulten.postgresql.core.context.ISharedSchemaContext;\nimport com.github.starnowski.posmulten.postgresql.core.context.DefaultSharedSchemaContextBuilder;\n//...\n    Map\u003cString, String\u003e usersTablePrimaryKeyNameToType = new HashMap();\n    usersTablePrimaryKeyNameToType.put(\"id\", \"bigint\");\n    Map\u003cString, String\u003e postsTablePrimaryKeyNameToType = new HashMap();\n    postsTablePrimaryKeyNameToType.put(\"id\", \"bigint\");\n    DefaultSharedSchemaContextBuilder defaultSharedSchemaContextBuilder = new DefaultSharedSchemaContextBuilder(null);\n    defaultSharedSchemaContextBuilder.setForceRowLevelSecurityForTableOwner(true);\n    defaultSharedSchemaContextBuilder.createRLSPolicyForTable(\"users\", usersTablePrimaryKeyNameToType, \"tenant_id\", \"users_table_rls_policy\");\n    defaultSharedSchemaContextBuilder.createRLSPolicyForTable(\"posts\", postsTablePrimaryKeyNameToType, \"tenant_id\", \"posts_table_rls_policy\");\n    //... other criteria\n```\nbuilder will produce \n```sql\nALTER TABLE \"users\" FORCE ROW LEVEL SECURITY;\nALTER TABLE \"posts\" FORCE ROW LEVEL SECURITY;\n```\n\n### Adding a foreign key constraint\nThe builder can create an additional constraint that checks if foreign key value references to the table row that belongs to the current tenant.\n```javadoc\ncreateSameTenantConstraintForForeignKey(String mainTable, String foreignKeyTable, Map\u003cString, String\u003e foreignKeyPrimaryKeyColumnsMappings, String constraintName)\n```\n\u003cb\u003emainTable\u003c/b\u003e - \u003cb\u003e(Required)\u003c/b\u003e table name that has foreign key columns.\u003cbr/\u003e\n\u003cb\u003eforeignKeyTable\u003c/b\u003e - \u003cb\u003e(Required)\u003c/b\u003e table name that is in relation.\u003cbr/\u003e\n\u003cb\u003eforeignKeyPrimaryKeyColumnsMappings\u003c/b\u003e - \u003cb\u003e(Required)\u003c/b\u003e map of foreign key columns and primary key columns from table which is in relation. The foreign key column name is the map key and the column name from relation table is its value.\u003cbr/\u003e\n\u003cb\u003econstraintName\u003c/b\u003e - \u003cb\u003e(Required)\u003c/b\u003e constraint name.\u003cbr/\u003e\n\nFor example, assuming that we have two tables, \"users\" and \"posts\":\n```sql\nCREATE TABLE public.users\n(\n    id bigint NOT NULL,\n    name character varying(255),\n    tenant_id character varying(255),\n    CONSTRAINT users_pkey PRIMARY KEY (id)\n)\nWITH (\n    OIDS = FALSE\n)\nTABLESPACE pg_default;\n\nCREATE TABLE public.posts\n(\n    id bigint NOT NULL,\n    text text NOT NULL,\n    user_id bigint NOT NULL,\n    tenant_id character varying(255),\n    CONSTRAINT fk_posts_user_id FOREIGN KEY (user_id)\n              REFERENCES users (id) MATCH SIMPLE,\n    CONSTRAINT posts_pkey PRIMARY KEY (id)\n)\nWITH (\n    OIDS = FALSE\n)\nTABLESPACE pg_default;\n```\n\nfor below criteria:\n```java\nimport com.github.starnowski.posmulten.postgresql.core.context.ISharedSchemaContext;\nimport com.github.starnowski.posmulten.postgresql.core.context.DefaultSharedSchemaContextBuilder;\n//...\n    Map\u003cString, String\u003e usersTablePrimaryKeyNameToType = new HashMap();\n    usersTablePrimaryKeyNameToType.put(\"id\", \"bigint\");\n    Map\u003cString, String\u003e postsTablePrimaryKeyNameToType = new HashMap();\n    postsTablePrimaryKeyNameToType.put(\"id\", \"bigint\");\n    DefaultSharedSchemaContextBuilder defaultSharedSchemaContextBuilder = new DefaultSharedSchemaContextBuilder(null);\n    defaultSharedSchemaContextBuilder.setGrantee(\"db_user\");\n    defaultSharedSchemaContextBuilder.createRLSPolicyForTable(\"users\", usersTablePrimaryKeyNameToType, \"tenant_id\", \"users_table_rls_policy\");\n    defaultSharedSchemaContextBuilder.createRLSPolicyForTable(\"posts\", postsTablePrimaryKeyNameToType, \"tenant_id\", \"posts_table_rls_policy\");\n    //... foreign key constraint declaration\n    Map\u003cString, String\u003e foreignKeyColumnToPrimaryKeyColumn = new HashMap();\n    foreignKeyColumnToPrimaryKeyColumn.put(\"user_id\", \"id\");\n    defaultSharedSchemaContextBuilder.createSameTenantConstraintForForeignKey(\"posts\", \"users\", foreignKeyColumnToPrimaryKeyColumn, \"posts_users_fk_cu\");\n\n    // setting name for function\n    defaultSharedSchemaContextBuilder.setNameForFunctionThatChecksIfRecordExistsInTable(\"users\", \"is_user_belongs_to_current_tenant\");\n```\nposmulten is going to produce the below statements related to foreign key constraint:\n```sql\nCREATE OR REPLACE FUNCTION is_user_belongs_to_current_tenant(bigint) RETURNS BOOLEAN AS $$\nSELECT EXISTS (\n\tSELECT 1 FROM users rt WHERE rt.id = $1 AND rt.tenant_id = get_current_tenant_id()\n)\n$$ LANGUAGE sql\nSTABLE\nPARALLEL SAFE;\n\nALTER TABLE \"posts\" ADD CONSTRAINT posts_users_fk_cu CHECK ((user_id IS NULL) OR (is_user_belongs_to_current_tenant(user_id)));\n```\nAs was mentioned in previous [sections](#setting-rls-policy-for-a-table-with-a-multi-column-primary-key), posmulten creates a function that checks if a row with a specified primary key exists for the current tenant.\nFor this example, the is_user_belongs_to_current_tenant function was created because table posts have a foreign key column that references table users.\nAt this moment, the [name](#setting-function-name-that-checks-if-passed-primary-key-for-a-specific-table-exists-for-the-current-tenant) for such function has to be specified; otherwise, the builder can throw an exception.\n\n#### Adding a foreign key constraint with a multi-column primary key\nBelow there is an example of how to specify a foreign key constraint when the key has many columns. \nThe comments table has a primary key with two columns.\n```sql\nCREATE TABLE public.comments\n(\nid int NOT NULL,\nuser_id bigint NOT NULL,\ntext text NOT NULL,\ntenant character varying(255),\n\nparent_comment_id int,\nparent_comment_user_id bigint,\n\nCONSTRAINT fk_comments_users_id FOREIGN KEY (user_id)\nREFERENCES public.users (id) MATCH SIMPLE,\n\nCONSTRAINT fk_comments_parent_id FOREIGN KEY (parent_comment_id, parent_comment_user_id)\nREFERENCES public.comments (id, user_id) MATCH SIMPLE,\n\nCONSTRAINT comments_pkey PRIMARY KEY (id, user_id)\n)\nWITH (\nOIDS = FALSE\n)\nTABLESPACE pg_default;\n```\nThe table has a relation to itself (the comment has its parent).\nBelow is an example of how to define foreign key constraints with the builder component.\n```java\nimport com.github.starnowski.posmulten.postgresql.core.context.ISharedSchemaContext;\nimport com.github.starnowski.posmulten.postgresql.core.context.DefaultSharedSchemaContextBuilder;\n//...\n\n    Map\u003cString, String\u003e commentsTablePrimaryKeyNameToType = new HashMap();\n    commentsTablePrimaryKeyNameToType.put(\"id\", \"int\");\n    commentsTablePrimaryKeyNameToType.put(\"user_id\", \"bigint\");\n    defaultSharedSchemaContextBuilder.createRLSPolicyForTable(\"comments\", commentsTablePrimaryKeyNameToType, \"tenant\", \"comments_table_rls_policy\");\n    Map\u003cString, String\u003e foreignKeyColumnToPrimaryKeyColumn = new HashMap();\n    foreignKeyColumnToPrimaryKeyColumn.put(\"parent_comment_id\", \"id\");\n    foreignKeyColumnToPrimaryKeyColumn.put(\"parent_comment_user_id\", \"user_id\");\n    defaultSharedSchemaContextBuilder.createSameTenantConstraintForForeignKey(\"comments\", \"comments\", foreignKeyColumnToPrimaryKeyColumn, \"comments_parent_comments_fk_cu\");\n```\nthe builder will produce the below statements:\n```sql\nCREATE OR REPLACE FUNCTION is_comment_belongs_to_current_tenant(bigint, int) RETURNS BOOLEAN AS $$\nSELECT EXISTS (\n\tSELECT 1 FROM comments rt WHERE rt.user_id = $1 AND rt.id = $2 AND rt.tenant = get_current_tenant_id()\n)\n$$ LANGUAGE sql\nSTABLE\nPARALLEL SAFE;\n--\nALTER TABLE \"comments\" ADD CONSTRAINT comments_parent_comments_fk_cu CHECK ((parent_comment_id IS NULL OR parent_comment_user_id IS NULL) OR (is_comment_belongs_to_current_tenant(parent_comment_user_id, parent_comment_id)));\n```\n\n### Setting of type for tenant identifier value\nBy default, the builder assumes that the tenant column type is going to be `VARCHAR(255)`.\nThis also the type for parameters of a few function:\n\n- [Function that checks tenant access to a table row](#function-that-checks-tenant-access-to-a-table-row)\n- [Function that checks if the passed identifier is the same as the current tenant identifier](#function-that-checks-if-the-passed-identifier-is-the-same-as-the-current-tenant-identifier)\n- [Function that set the current tenant identifier](#function-that-set-the-current-tenant-identifier)\n\nIt is also return type for function [function that returns the current tenant identifier](#function-that-returns-the-current-tenant-identifier).\nThe method that set this type is:\n\n```javadoc\ncom.github.starnowski.posmulten.postgresql.core.context.DefaultSharedSchemaContextBuilder#setCurrentTenantIdPropertyType(String currentTenantIdPropertyType\n```\n\nFor example, if the value is going to be set to the \"UUID\" then the builder is going to produce a statement like this:\n\n```sql\nCREATE OR REPLACE FUNCTION get_current_tenant_id() RETURNS UUID AS $$\nSELECT current_setting('c.c_ten')\n$$ LANGUAGE sql\nSTABLE\nPARALLEL SAFE;\n\nCREATE OR REPLACE FUNCTION set_current_tenant_id(UUID) RETURNS VOID AS $$\nBEGIN\nPERFORM set_config('c.c_ten', $1, false);\nEND\n$$ LANGUAGE plpgsql\nVOLATILE;\n```\n\n### Setting the property name that stores tenant identifier value\nBy default builder use property name \"posmulten.tenant_id\" to set current tenant identifier.\nThis property is used in function that [set current tenant identifier](#function-that-set-the-current-tenant-identifier) or [gets its value](#function-that-returns-the-current-tenant-identifier).\n```javadoc\ncom.github.starnowski.posmulten.postgresql.core.context.DefaultSharedSchemaContextBuilder#setCurrentTenantIdProperty(String currentTenantIdProperty)\n```\n\n### Adding default value for tenant column\nThe builder can create statements that add default values statements for the tenant column in each table. \nBy default, the builder does not do that. \nTo specify behavior builder use method:\n```javadoc\ncom.github.starnowski.posmulten.postgresql.core.context.DefaultSharedSchemaContextBuilder#setCurrentTenantIdentifierAsDefaultValueForTenantColumnInAllTables(boolean value)\n```\nFor example, for database tables below:\n```sql\nCREATE TABLE public.users\n(\n    id bigint NOT NULL,\n    name character varying(255),\n    tenant_id character varying(255),\n    CONSTRAINT users_pkey PRIMARY KEY (id)\n)\nWITH (\n    OIDS = FALSE\n)\nTABLESPACE pg_default;\n\nCREATE TABLE public.posts\n(\n    id bigint NOT NULL,\n    text text NOT NULL,\n    user_id bigint NOT NULL,\n    tenant_id character varying(255),\n    CONSTRAINT fk_posts_user_id FOREIGN KEY (user_id)\n              REFERENCES users (id) MATCH SIMPLE,\n    CONSTRAINT posts_pkey PRIMARY KEY (id)\n)\nWITH (\n    OIDS = FALSE\n)\nTABLESPACE pg_default;\n```\nand builder criteria:\n```java\nimport com.github.starnowski.posmulten.postgresql.core.context.ISharedSchemaContext;\nimport com.github.starnowski.posmulten.postgresql.core.context.DefaultSharedSchemaContextBuilder;\n    Map\u003cString, String\u003e usersTablePrimaryKeyNameToType = new HashMap();\n    usersTablePrimaryKeyNameToType.put(\"id\", \"bigint\");\n    Map\u003cString, String\u003e postsTablePrimaryKeyNameToType = new HashMap();\n    postsTablePrimaryKeyNameToType.put(\"id\", \"bigint\");\n    DefaultSharedSchemaContextBuilder defaultSharedSchemaContextBuilder = new DefaultSharedSchemaContextBuilder(null);\n    defaultSharedSchemaContextBuilder.createRLSPolicyForTable(\"users\", usersTablePrimaryKeyNameToType, \"tenant_id\", \"users_table_rls_policy\");\n    defaultSharedSchemaContextBuilder.createRLSPolicyForTable(\"posts\", postsTablePrimaryKeyNameToType, \"tenant_id\", \"posts_table_rls_policy\");\n//... other criteria\n    defaultSharedSchemaContextBuilder.setCurrentTenantIdentifierAsDefaultValueForTenantColumnInAllTables(true);\n```\nthe builder will produce below statements:\n```sql\nALTER TABLE users ALTER COLUMN tenant_id SET DEFAULT get_current_tenant_id();\nALTER TABLE posts ALTER COLUMN tenant_id SET DEFAULT get_current_tenant_id();\n```\n\n#### Skipping adding default value for tenant column for a single table\nThe builder has a method that allows skipping the creation of the default value statements for a specific table when the option of adding default value for all tenant columns is on.\n```javadoc\ncom.github.starnowski.posmulten.postgresql.core.context.DefaultSharedSchemaContextBuilder#skipAddingOfTenantColumnDefaultValueForTable(String value)\n```\nFor below criteria:\n```java\nimport com.github.starnowski.posmulten.postgresql.core.context.ISharedSchemaContext;\nimport com.github.starnowski.posmulten.postgresql.core.context.DefaultSharedSchemaContextBuilder;\n    Map\u003cString, String\u003e usersTablePrimaryKeyNameToType = new HashMap();\n    usersTablePrimaryKeyNameToType.put(\"id\", \"bigint\");\n    Map\u003cString, String\u003e postsTablePrimaryKeyNameToType = new HashMap();\n    postsTablePrimaryKeyNameToType.put(\"id\", \"bigint\");\n    DefaultSharedSchemaContextBuilder defaultSharedSchemaContextBuilder = new DefaultSharedSchemaContextBuilder(null);\n    defaultSharedSchemaContextBuilder.createRLSPolicyForTable(\"users\", usersTablePrimaryKeyNameToType, \"tenant_id\", \"users_table_rls_policy\");\n    defaultSharedSchemaContextBuilder.createRLSPolicyForTable(\"posts\", postsTablePrimaryKeyNameToType, \"tenant_id\", \"posts_table_rls_policy\");\n//... other criteria\n    defaultSharedSchemaContextBuilder.setCurrentTenantIdentifierAsDefaultValueForTenantColumnInAllTables(true);\n    defaultSharedSchemaContextBuilder.skipAddingOfTenantColumnDefaultValueForTable(\"posts\");\n```\nthe builder will produce below statements:\n```sql\nALTER TABLE users ALTER COLUMN tenant_id SET DEFAULT get_current_tenant_id();\n```\nbuilder will not produce a statement for the posts table.\n\n### Adding tenant column to tenant table\nThe builder can produce a statement that creates a tenant column for a table that does not have it.\n```javadoc\ncom.github.starnowski.posmulten.postgresql.core.context.DefaultSharedSchemaContextBuilder#createTenantColumnForTable(String table)\n```\nFor below criteria:\n```java\nimport com.github.starnowski.posmulten.postgresql.core.context.ISharedSchemaContext;\nimport com.github.starnowski.posmulten.postgresql.core.context.DefaultSharedSchemaContextBuilder;\n    Map\u003cString, String\u003e notificationsTablePrimaryKeyNameToType = new HashMap();\n    notificationsTablePrimaryKeyNameToType.put(\"uuid\", \"uuid\");\n    DefaultSharedSchemaContextBuilder defaultSharedSchemaContextBuilder = new DefaultSharedSchemaContextBuilder(null);\n    defaultSharedSchemaContextBuilder.createTenantColumnForTable(\"notifications\")\n        .createRLSPolicyForTable(\"notifications\", notificationsTablePrimaryKeyNameToType, \"tenant_x\", \"notifications_table_rls_policy\")\n//... other criteria\n```\nthe builder will produce below statements:\n```sql\nALTER TABLE notifications ADD COLUMN tenant_x VARCHAR(255);\nALTER TABLE notifications ALTER COLUMN tenant_x SET NOT NULL;\nALTER TABLE notifications ALTER COLUMN tenant_x SET DEFAULT get_current_tenant_id();\n```\n\n### Setting default tenant column name\nBy default tenant, the builder assumes that tenant column name is \"tenant_id\".\nIt is important during [adding tenant column](#adding-default-value-for-tenant-column).\nOr specifying [RLS policy](#setting-rls-policy-for-table) for a table without specifying a tenant column name. \nIn such a case, the builder assumes that the tenant column for such a table is equal to the default value. \nThe builder allows to change default tenant column name via method:\n```javadoc\ncom.github.starnowski.posmulten.postgresql.core.context.DefaultSharedSchemaContextBuilder#setDefaultTenantIdColumn(String defaultTenantIdColumn)\n```\n\n### Setting function name that returns the current tenant identifier\nThe builder allows to set the name of [function that returns the current tenant identifier](#function-that-returns-the-current-tenant-identifier) via method:\n```javadoc\ncom.github.starnowski.posmulten.postgresql.core.context.DefaultSharedSchemaContextBuilder#setGetCurrentTenantIdFunctionName(String getCurrentTenantIdFunctionName)\n```\n\n### Setting function name that sets the current tenant identifier\nThe builder allows to set the name of [function that set the current tenant identifier](#function-that-set-the-current-tenant-identifier) via method:\n```javadoc\ncom.github.starnowski.posmulten.postgresql.core.context.DefaultSharedSchemaContextBuilder#setSetCurrentTenantIdFunctionName(String setCurrentTenantIdFunctionName)\n```\n\n### Setting function name that checks if current tenant has authorities to a table row\nThe builder allows to set the name of [function that checks tenant access to a table row](#function-that-checks-tenant-access-to-a-table-row) via method:\n```javadoc\ncom.github.starnowski.posmulten.postgresql.core.context.DefaultSharedSchemaContextBuilder#setTenantHasAuthoritiesFunctionName(String tenantHasAuthoritiesFunctionName)\n```\n\n### Setting function name that checks if passed identifier is the same as current tenant identifier\nThe builder allows to set the name of [function that checks if the passed identifier is the same as the current tenant identifier](#function-that-checks-if-the-passed-identifier-is-the-same-as-the-current-tenant-identifier) via method:\n```javadoc\ncom.github.starnowski.posmulten.postgresql.core.context.DefaultSharedSchemaContextBuilder#setEqualsCurrentTenantIdentifierFunctionName(String equalsCurrentTenantIdentifierFunctionName)\n```\n\n### Setting function name that checks if passed primary key for a specific table exists for the current tenant\nIt is required to specify the function that checks if the passed identifier exists in a specific table.\nThe builder has a single method for this purpose.\n```javadoc\ncom.github.starnowski.posmulten.postgresql.core.context.DefaultSharedSchemaContextBuilder#setNameForFunctionThatChecksIfRecordExistsInTable(String recordTable, String functionName)\n```\n\u003cb\u003erecordTable\u003c/b\u003e - \u003cb\u003e(Required)\u003c/b\u003e table name.\u003cbr/\u003e\n\u003cb\u003efunctionName\u003c/b\u003e - \u003cb\u003e(Required)\u003c/b\u003e function name.\u003cbr/\u003e\nTODO\n\n### Setting a list of invalid tenant identifier values\nThe builder allows to specify the list of invalid tenant identifier values via method:\n```javadoc\ncom.github.starnowski.posmulten.postgresql.core.context.DefaultSharedSchemaContextBuilder#createValidTenantValueConstraint(List\u003cString\u003e tenantValuesBlacklist, String isTenantValidFunctionName, String isTenantValidConstraintName)\n```\n\u003cb\u003etenantValuesBlacklist\u003c/b\u003e - \u003cb\u003e(Required)\u003c/b\u003e list of invalid tenant identifier values.\u003cbr/\u003e\n\u003cb\u003eisTenantValidFunctionName\u003c/b\u003e - \u003cb\u003e(Optional)\u003c/b\u003e name of the function that checks if passed tenant identifier is valid. If passed value is null then function name is \"is_tenant_identifier_valid\".\u003cbr/\u003e\n\u003cb\u003eisTenantValidConstraintName\u003c/b\u003e - \u003cb\u003e(Optional)\u003c/b\u003e name of the constraint that checks if the tenant column has a valid value. If the passed value is null then the constraint name is \"tenant_identifier_valid\".\u003cbr/\u003e\n\nFor below criteria:\n```java\nimport com.github.starnowski.posmulten.postgresql.core.context.ISharedSchemaContext;\nimport com.github.starnowski.posmulten.postgresql.core.context.DefaultSharedSchemaContextBuilder;\n    Map\u003cString, String\u003e usersTablePrimaryKeyNameToType = new HashMap();\n    usersTablePrimaryKeyNameToType.put(\"id\", \"bigint\");\n    Map\u003cString, String\u003e postsTablePrimaryKeyNameToType = new HashMap();\n    postsTablePrimaryKeyNameToType.put(\"id\", \"bigint\");\n    DefaultSharedSchemaContextBuilder defaultSharedSchemaContextBuilder = new DefaultSharedSchemaContextBuilder(null);\n    defaultSharedSchemaContextBuilder.createRLSPolicyForTable(\"users\", usersTablePrimaryKeyNameToType, \"tenant_id\", \"users_table_rls_policy\");\n    defaultSharedSchemaContextBuilder.createRLSPolicyForTable(\"posts\", postsTablePrimaryKeyNameToType, \"tenant_id\", \"posts_table_rls_policy\");\n//... other criteria\n    defaultSharedSchemaContextBuilder.createValidTenantValueConstraint(asList(\"DUMMMY_TENANT\",  \"XXX-INVAlid_tenant\"), null, null);\n```\nthe builder will produce below statements:\n```sql\nCREATE OR REPLACE FUNCTION is_tenant_id_valid(VARCHAR(255)) RETURNS BOOLEAN AS $$\nSELECT $1 \u003c\u003e CAST ('DUMMMY_TENANT' AS VARCHAR(255)) AND $1 \u003c\u003e CAST ('XXX-INVAlid_tenant' AS VARCHAR(255))\n$$ LANGUAGE sql\nIMMUTABLE\nPARALLEL SAFE;\nALTER TABLE \"users\" ADD CONSTRAINT tenant_should_be_valid CHECK (tenant_id IS NULL OR is_tenant_id_valid(tenant_id));\nALTER TABLE \"posts\" ADD CONSTRAINT tenant_should_be_valid CHECK (tenant_id IS NULL OR is_tenant_id_valid(tenant_id));\n```\n#### Setting custom name for table tenant column constraint\nBuilder allows specifying custom constraint name via the method:\n```javadoc\ncom.github.starnowski.posmulten.postgresql.core.context.DefaultSharedSchemaContextBuilder#registerCustomValidTenantValueConstraintNameForTable(String table, String constraintName)\n```\n\u003cb\u003etable\u003c/b\u003e - \u003cb\u003e(Required)\u003c/b\u003e name of table.\u003cbr/\u003e\n\u003cb\u003econstraintName\u003c/b\u003e - \u003cb\u003e(Required)\u003c/b\u003e name of constraint.\u003cbr/\u003e\n\nfor below criteria:\n```java\nimport com.github.starnowski.posmulten.postgresql.core.context.ISharedSchemaContext;\nimport com.github.starnowski.posmulten.postgresql.core.context.DefaultSharedSchemaContextBuilder;\n    Map\u003cString, String\u003e usersTablePrimaryKeyNameToType = new HashMap();\n    usersTablePrimaryKeyNameToType.put(\"id\", \"bigint\");\n    Map\u003cString, String\u003e postsTablePrimaryKeyNameToType = new HashMap();\n    postsTablePrimaryKeyNameToType.put(\"id\", \"bigint\");\n    DefaultSharedSchemaContextBuilder defaultSharedSchemaContextBuilder = new DefaultSharedSchemaContextBuilder(null);\n    defaultSharedSchemaContextBuilder.createRLSPolicyForTable(\"users\", usersTablePrimaryKeyNameToType, \"tenant_id\", \"users_table_rls_policy\");\n    defaultSharedSchemaContextBuilder.createRLSPolicyForTable(\"posts\", postsTablePrimaryKeyNameToType, \"tenant_id\", \"posts_table_rls_policy\");\n    defaultSharedSchemaContextBuilder.createValidTenantValueConstraint(asList(\"DUMMMY_TENANT\",  \"XXX-INVAlid_tenant\"), null, null);\n//... other criteria\n    defaultSharedSchemaContextBuilder.registerCustomValidTenantValueConstraintNameForTable(\"posts\", \"posts_tenant_is_valid\");\n```\nthe builder will produce below statements:\n```sql\nALTER TABLE \"users\" ADD CONSTRAINT tenant_should_be_valid CHECK (tenant_id IS NULL OR is_tenant_id_valid(tenant_id));\nALTER TABLE \"posts\" ADD CONSTRAINT posts_tenant_is_valid CHECK (tenant_id IS NULL OR is_tenant_id_valid(tenant_id));\n```\n\n#### Setting foreign key constraint where tenant column is part of composite key\nThere might be a situation when your database model assumes that the tenant column is part of the table's primary key and, that being said, also part of the foreign keys.\nThis means that, for example, all your unique constraint has to cover tenant column value.\nHaving such a database model allows easier migration of tenant data between databases.\nBelow there is example how to specify such shared context object:\nWe have two tables, \"users\" and \"notifications\".\n\n```java\n        DefaultSharedSchemaContextBuilder defaultSharedSchemaContextBuilder = new DefaultSharedSchemaContextBuilder(null);\n\n        // Setting flag with true value\n        defaultSharedSchemaContextBuilder.setCreateForeignKeyConstraintWithTenantColumn(true);\n        \n        //....\n        // We have two tables, \"users\" and \"notifications\".\n        // Setting tables for which RLS should be created\n        defaultSharedSchemaContextBuilder.createRLSPolicyForTable(\"users\", mapBuilder().put((\"id\", \"bigint\").build() , \"tenant_id\", \"users_table_rls_policy\");\n        defaultSharedSchemaContextBuilder.createRLSPolicyForTable(\"notifications\", mapBuilder().put((\"uuid\", \"uuid\").build(), \"notification_tenant\", \"notifications_table_rls_policy\");\n        \n        // Setting foreign key constraint declaration with method 'createSameTenantConstraintForForeignKey'\n        defaultSharedSchemaContextBuilder.createSameTenantConstraintForForeignKey(\"notification, \"users\", mapBuilder().put(\"user_id\", \"id\").build(), \"notification_users_fk\");\n\n        // Setting a name for a function that checks if user record (reference table for our foreign key example) exists is not needed\n        //defaultSharedSchemaContextBuilder.setNameForFunctionThatChecksIfRecordExistsInTable(\"users\", \"is_user_belongs_to_current_tenant\");\n```\n\nthe builder will produce below statements:\n\n```sql\nALTER TABLE IF EXISTS \"notifications\" ADD CONSTRAINT notification_users_fk FOREIGN KEY (notification_tenant, user_id) REFERENCES \"users\" (tenant_id, id) MATCH SIMPLE;\n```\n\n\n### Naming convention and its constraints\nBy default function name can have a length from 1 to 63 characters. \nSQL definitions validation can be disabled by using method:\n```javadoc\ncom.github.starnowski.posmulten.postgresql.core.context.DefaultSharedSchemaContextBuilder#setDisableDefaultSqlDefinitionsValidators(boolean disableDefaultSqlDefinitionsValidators)\n```\nit can be also disabled by setting null or empty list by method.\n```javadoc\ncom.github.starnowski.posmulten.postgresql.core.context.DefaultSharedSchemaContextBuilder#setSqlDefinitionsValidators(List\u003cISQLDefinitionsValidator\u003e sqlDefinitionsValidators)\n```\nBy using this method there is also the possibility of customization for validation.\n\n# Adding custom sql definitions\n\nThere is an option to pass custom SQL definition to query builder.\n\n```javadoc\ncom.github.starnowski.posmulten.postgresql.core.context.DefaultSharedSchemaContextBuilder#addCustomSQLDefinition(com.github.starnowski.posmulten.postgresql.core.context.CustomSQLDefinitionPairPositionProvider positionProvider, String creationScript)\ncom.github.starnowski.posmulten.postgresql.core.context.DefaultSharedSchemaContextBuilder#addCustomSQLDefinition(com.github.starnowski.posmulten.postgresql.core.context.CustomSQLDefinitionPairPositionProvider positionProvider, String creationScript, String dropScript)\ncom.github.starnowski.posmulten.postgresql.core.context.DefaultSharedSchemaContextBuilder#addCustomSQLDefinition(com.github.starnowski.posmulten.postgresql.core.context.CustomSQLDefinitionPairPositionProvider positionProvider, String creationScript, String dropScript, List\u003cString\u003e checkingStatements)\ncom.github.starnowski.posmulten.postgresql.core.context.DefaultSharedSchemaContextBuilder#addCustomSQLDefinition(com.github.starnowski.posmulten.postgresql.core.context.CustomSQLDefinitionPairPositionProvider positionProvider, com.github.starnowski.posmulten.postgresql.core.common.SQLDefinition sqlDefinition)\n```\n\nEach method pass object of type com.github.starnowski.posmulten.postgresql.core.context.CustomSQLDefinitionPairPositionProvider that returns position for SQL definition.\nDefault implementation of this type is com.github.starnowski.posmulten.postgresql.core.context.CustomSQLDefinitionPairDefaultPosition enum type that has two values:\n    \n    AT_BEGINNING - custom definition is being added before all definitions created by builder\n    AT_END - custom definition is being added after all definitions created by builder\n\nExample:\n```javadoc\n        ISharedSchemaContext result = (new DefaultSharedSchemaContextBuilder(null))\n                .setGrantee(CORE_OWNER_USER)\n                .addCustomSQLDefinition(CustomSQLDefinitionPairDefaultPosition.AT_BEGINNING, \"ALTER TABLE users ADD COLUMN custom_column1 VARCHAR(255);\", \"ALTER TABLE users DROP COLUMN custom_column1;\",\n                        singletonList(\"SELECT COUNT(1) FROM information_schema.columns WHERE table_catalog = 'postgresql_core' AND table_schema = 'public' AND table_name = 'users' AND column_name = 'custom_column1';\"))\n                .addCustomSQLDefinition(CustomSQLDefinitionPairDefaultPosition.AT_END, \"ALTER TABLE users ADD COLUMN custom_column2 VARCHAR(255);\", \"ALTER TABLE users DROP COLUMN custom_column2;\",\n                        singletonList(\"SELECT COUNT(1) FROM information_schema.columns WHERE table_catalog = 'postgresql_core' AND table_schema = 'public' AND table_name = 'users' AND column_name = 'custom_column2';\"));\n```\n\nThe method that passes only the creation script or creation and drop script for other missing scripts set \"SELECT 1\" as the default script.\nWhether the drop script or scripts, check if the creation script was applied correctly.\n\n# Using template variables in context builder\n\nVery often database schema names can be different depending on the environment, the database schema can have a different name for the dev environment than for the production environment.\nFor example schema can have value \"{{template_schema_value}}\" and db user can have \"{{template_user_grantee}}\".\n\n```sql\nCREATE OR REPLACE FUNCTION {{template_schema_value}}.set_tenant(VARCHAR(255)) RETURNS VOID AS $$\nBEGIN\nPERFORM set_config('pos.c.ten', $1, false);\nEND\n$$ LANGUAGE plpgsql\nVOLATILE;\n\nCREATE POLICY comments_table_rls_policy ON {{template_schema_value}}.comments\nFOR ALL\nTO \"{{template_user_grantee}}\"\nUSING ({{template_schema_value}}._tenant_hast_auth(tenant, 'ALL', 'USING', 'comments', '{{template_schema_value}}'))\nWITH CHECK ({{template_schema_value}}._tenant_hast_auth(tenant, 'ALL', 'WITH_CHECK', 'comments', '{{template_schema_value}}'));\n...\n```\n\nSuch generated statements can be used with [Liquibase](https://www.liquibase.org/) or [Flyway](https://flywaydb.org/).\nBoth those tools allow specifying configuration variables that be injected into scripts before executing them.\nPosmulten also has decorator types that can replace template variables with passed values.\nSuch a feature can be useful when there is a need to run generated scripts during application tests.\nThe below code demonstrates it:\n\n```java\nimport com.github.starnowski.posmulten.configuration.DefaultSharedSchemaContextBuilderFactoryResolver;\nimport com.github.starnowski.posmulten.configuration.NoDefaultSharedSchemaContextBuilderFactorySupplierException;\nimport com.github.starnowski.posmulten.configuration.core.context.IDefaultSharedSchemaContextBuilderFactory;\nimport com.github.starnowski.posmulten.configuration.core.exceptions.InvalidConfigurationException;\nimport com.github.starnowski.posmulten.postgresql.core.common.SQLDefinition;\nimport com.github.starnowski.posmulten.postgresql.core.context.DefaultSharedSchemaContextBuilder;\nimport com.github.starnowski.posmulten.postgresql.core.context.ISharedSchemaContext;\nimport com.github.starnowski.posmulten.postgresql.core.context.decorator.DefaultDecoratorContext;\nimport com.github.starnowski.posmulten.postgresql.core.context.decorator.SharedSchemaContextDecoratorFactory;\nimport com.github.starnowski.posmulten.postgresql.core.context.exceptions.SharedSchemaContextBuilderException;\nimport com.github.starnowski.posmulten.postgresql.core.db.DatabaseOperationExecutor;\nimport com.github.starnowski.posmulten.postgresql.core.db.operations.exceptions.ValidationDatabaseOperationsException;\nimport com.github.starnowski.posmulten.postgresql.core.rls.function.ISetCurrentTenantIdFunctionInvocationFactory;\nimport com.github.starnowski.posmulten.postgresql.test.utils.MapBuilder;\n\nimport javax.sql.DataSource;\nimport java.net.URISyntaxException;\nimport java.nio.file.Paths;\n/...\n\n\n    public void createSQLDefinitions() throws SharedSchemaContextBuilderException, NoDefaultSharedSchemaContextBuilderFactorySupplierException, InvalidConfigurationException, URISyntaxException, ValidationDatabaseOperationsException, SQLException {\n        DefaultSharedSchemaContextBuilderFactoryResolver defaultSharedSchemaContextBuilderFactoryResolver = new DefaultSharedSchemaContextBuilderFactoryResolver();\n        IDefaultSharedSchemaContextBuilderFactory factory = defaultSharedSchemaContextBuilderFactoryResolver.resolve(INTEGRATION_CONFIGURATION_TEST_FILE_PATH);\n        DefaultSharedSchemaContextBuilder builder = factory.build(resolveFilePath(INTEGRATION_CONFIGURATION_TEST_FILE_PATH));\n        DefaultDecoratorContext decoratorContext = DefaultDecoratorContext.builder()\n                .withReplaceCharactersMap(MapBuilder.mapBuilder()\n                        .put(\"{{template_schema_value}}\", \"non_public_schema\")\n                        .put(\"{{template_user_grantee}}\", CORE_OWNER_USER)\n                        .build())\n                .build();\n        ISharedSchemaContext defaultContext = builder.build();\n        // Display original SQL statements\n        this.databaseOperationExecutor.execute(dataSource, defaultContext.getSqlDefinitions(), LOG_ALL);\n        SharedSchemaContextDecoratorFactory sharedSchemaContextDecoratorFactory = new SharedSchemaContextDecoratorFactory();\n        sharedSchemaContext = sharedSchemaContextDecoratorFactory.build(defaultContext, decoratorContext);\n        setCurrentTenantIdFunctionInvocationFactory = sharedSchemaContext.getISetCurrentTenantIdFunctionInvocationFactory();\n        sqlDefinitions.addAll(sharedSchemaContext.getSqlDefinitions());\n    }\n\n\n    public void executeSQLDefinitions() {\n            try {\n            this.databaseOperationExecutor.execute(dataSource, sharedSchemaContext.getSqlDefinitions(), CREATE);\n            this.databaseOperationExecutor.execute(dataSource, sharedSchemaContext.getSqlDefinitions(), LOG_ALL);\n            this.databaseOperationExecutor.execute(dataSource, sharedSchemaContext.getSqlDefinitions(), VALIDATE);\n            } catch (SQLException e) {\n            throw new RuntimeException(e);\n            } catch (ValidationDatabaseOperationsException e) {\n            throw new RuntimeException(e);\n            }\n            }\n```\n\nLibrary will generate SQL statements with replaced values:\n\n```sql\nCREATE OR REPLACE FUNCTION non_public_schema.get_ten_id() RETURNS VARCHAR(255) AS $$\nSELECT current_setting('pos.c.ten')\n$$ LANGUAGE sql\nSTABLE\nPARALLEL SAFE;\n\nCREATE POLICY comments_table_rls_policy ON non_public_schema.comments\nFOR ALL\nTO \"postgresql-core-owner\"\nUSING (non_public_schema._tenant_hast_auth(tenant, 'ALL', 'USING', 'comments', 'non_public_schema'))\nWITH CHECK (non_public_schema._tenant_hast_auth(tenant, 'ALL', 'WITH_CHECK', 'comments', 'non_public_schema'));\n...\n```\n\nPlease be in mind that above code use [configuration](configuration-parent/configuration) and [configuration-yaml-interpreter](configuration-parent/configuration-yaml-interpreter) modules.\nWhere configuration for th Posmulten library is store in Yaml file.\nHowever, you can also pass template values as parameters to DefaultSharedSchemaContextBuilder methods.\n\n# GUI\nThere is a GUI application implemented in the Swing library. \nFor more details, please check the [openwebstart](openwebstart) module.\n\n# Reporting issues\n* Any new issues please report in [GitHub site](https://github.com/starnowski/posmulten/issues)\n\n# Project contribution\n* Look for open issues or create your own\n* Fork repository on Github and start applying your changes to master branch or release branch\n* Follow CONTRIBUTING.md document for coding rules\n* Create pull request\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstarnowski%2Fposmulten","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fstarnowski%2Fposmulten","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstarnowski%2Fposmulten/lists"}