Class Db<T extends Db>
- All Implemented Interfaces:
Comparable<T>
The goal of the Db abstraction is to allow Actions like DbGet/Put/Post/Patch/DeleteAction to apply the same REST CRUD operations agnostically across multiple backend data storage engines.
The primary job of a Db subclass is to:
- reflectively generate Collections to represent their underlying tables (or buckets, folders, containers etc.) columns, indexes, and relationships, during
#doStartup(Api). - implement REST CRUD support by implementing
select(Collection, Map),upsert(Collection, List),delete(Collection, List).
Actions such as DbGetAction then:
- translate a REST collection/resource request into columnName based json and RQL,
- execute the requested CRUD method on the Collection's underlying Db
- then translate the results back from the Db columnName based data into the approperiate jsonName version for external consumption.
-
Nested Class Summary
Nested classes/interfaces inherited from class io.inversion.Rule
Rule.RuleMatcher -
Field Summary
FieldsModifier and TypeFieldDescriptionprotected booleanIndicates that this Db should reflectively create and configure Collections to represent its underlying tables.protected final ArrayList<Collection>The Collections that are the REST interface to the backend tables (or buckets, folders, containers etc.) this Db exposes through an Api.protected booleanWhen set to true the Db will do everything it can to "work offline" logging commands it would have run but not actually running them.OPTIONAL column names that should be excluded from RQL queries, upserts and patches.OPTIONAL column names that should be included in RQL queries, upserts and patches.A tableName to collectionName map that can be used by whitelist backend tables that should be included in reflective Collection creation.protected final org.slf4j.LoggerThese params are specifically NOT passed to the Query for parsing.protected StringA property that can be used to disambiguate different backends supported by a single subclass.Fields inherited from class io.inversion.Rule
ALL_METHODS, configMap, description, excludeMatchers, excludeOn, includeMatchers, includeOn, name, order, params -
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionprotected StringbeautifyCollectionName(String tableName) Attempts to camelCase the table name to make it an attractive REST collection name.protected StringbeautifyName(String name) Try to make an attractive camelCase valid javascript variable name.protected voidCreates a collection for every table name inincludeTablesgiving the Collections and Properties beautified JSON names.protected voidCreates ONE_TO_MANY, MANY_TO_ONE, and MANY_TO_MANY Relationships with attractive RESTish names based on the primary Index and foreign key Index structures of the Collections.castDbOutput(Property property, Object value) Casts value as Property.typecastJsonInput(Property property, Object value) Casts value as Property.type.static ObjectcastJsonInput(String type, Object value) protected voidDoes some final configuration adds all non excluded Collections to the Api via Api.withCollectionprotected voidconfigDb()Subclasses should reflectively create Collections and Properties, Indexes, and Relationships here.final voidDeletes rows identified by the unique index values from the underlying data source.voiddoSelect(Collection collection, List<io.inversion.rql.Term> queryTerms) Finds all records that match the supplied RQL query terms.protected voidprotected voiddoShutdown(Api api) Made to be overridden by subclasses or anonymous inner classes to do specific cleanupprotected voidMade to be overridden by subclasses or anonymous inner classes to do specific init of an Api.Upserts the key/values pairs for each record into the underlying data source.protected booleanexcludeTable(String tableName) booleanfilterOutJsonProperty(Collection collection, String name) Checks if "collectionName.columnName" or just "columnName" is specifically included or excluded viaincludeColumnsexcludeColumnsor is a valid Property columnName.getCollection(String collectionOrTableName) getCollectionByTableName(String tableName) protected PropertygetProperty(String tableName, String columnName) getType()booleanbooleanisDryRun()booleanbooleanprotected StringmakeRelationshipName(Collection collection, Relationship relationship) Attempts to construct a sensible json property name for a Relationship.protected Set<io.inversion.rql.Term>mapToColumnNames(Collection collection, io.inversion.rql.Term term) mapToColumnNames(Collection collection, List<Map<String, Object>> records) protected voidmapToJsonNames(Collection collection, io.inversion.rql.Term term) Should be called by Actions instead of upsert() only when all records are strictly known to exist.voidremoveCollection(Collection table) final Resultsselect(Collection collection, Map<String, String> params) Finds all records that match the supplied RQL query terms.shutdown()Shutsdown all running Apis.final TCalled by an Api to as part of Api.startup().withBootstrap(boolean bootstrap) withCollection(Collection collection) withCollections(Collection... collections) withDryRun(boolean dryRun) withExcludeColumns(String... columnNames) withIncludeColumns(String... columnNames) withIncludeTable(String tableName, String collectionName) Whitelists tableName as an underlying table that should be reflectively bootstrapped and exposed as collectionName.withIncludeTables(String... includeTables) Utility that parses a comma and pipe separated list of table name to collection name mappings.Methods inherited from class io.inversion.Rule
afterWiringComplete, checkLazyConfig, compareTo, doLazyConfig, getAllExcludePaths, getAllIncludeMethods, getAllIncludePaths, getDefaultIncludeMatchers, getDescription, getExcludeMatchers, getIncludeMatchers, getName, getOrder, getParams, match, match, matches, matches, toString, withDescription, withExcludeOn, withExcludeOn, withIncludeOn, withIncludeOn, withName, withOrder, withParam, withParams
-
Field Details
-
reservedParams
These params are specifically NOT passed to the Query for parsing. These are either dirty worlds like sql injection tokens or the are used by actions themselves -
log
protected final org.slf4j.Logger log -
collections
The Collections that are the REST interface to the backend tables (or buckets, folders, containers etc.) this Db exposes through an Api. -
includeTables
A tableName to collectionName map that can be used by whitelist backend tables that should be included in reflective Collection creation. -
includeColumns
OPTIONAL column names that should be included in RQL queries, upserts and patches. -
excludeColumns
OPTIONAL column names that should be excluded from RQL queries, upserts and patches. -
bootstrap
protected boolean bootstrapIndicates that this Db should reflectively create and configure Collections to represent its underlying tables.This would be false when an Api designer wants to very specifically configure an Api probably when the underlying db does not support the type of reflection required. For example, you may want to put specific Property and Relationship structure on top of an unstructured JSON document store.
-
type
A property that can be used to disambiguate different backends supported by a single subclass.For example type might be "mysql" for a JdbcDb.
-
dryRun
protected boolean dryRunWhen set to true the Db will do everything it can to "work offline" logging commands it would have run but not actually running them.
-
-
Constructor Details
-
Db
public Db() -
Db
-
-
Method Details
-
excludeTable
-
castJsonInput
-
startup
Called by an Api to as part of Api.startup().This implementation really only manages starting/started state, with the heaving lifting of bootstrapping delegated to
doStartup(Api).- Parameters:
api- the api to start- Returns:
- this
- See Also:
-
doStartup
Made to be overridden by subclasses or anonymous inner classes to do specific init of an Api.This method will not be called a second time after for an Api unless the Api is shutdown and then restarted.
The default implementation, when
isBootstrap()is true, callsconfigDb()once globally andconfigApi(Api)once for each Api passed in.- Parameters:
api- the api to start- See Also:
-
shutdown
Shutsdown all running Apis.This is primarily a method used for testing.
- Returns:
- this
-
doShutdown
protected void doShutdown() -
shutdown
-
doShutdown
Made to be overridden by subclasses or anonymous inner classes to do specific cleanup- Parameters:
api- the api shutting down
-
isRunning
-
select
Finds all records that match the supplied RQL query terms.The implementation of this method primarily translates jsonNames to columnNames for RQL inputs and JSON outputs delegating the work to
doSelect(Collection, List)where all ins and outs are based on columnName.- Parameters:
collection- the collection being queriedparams- RQL terms that have been translated to use Property jsonNames- Returns:
- A list of maps with keys as Property jsonNames
- Throws:
ApiException- TODO: update/correct this javadoc
-
doSelect
public Results doSelect(Collection collection, List<io.inversion.rql.Term> queryTerms) throws ApiException Finds all records that match the supplied RQL query terms.- Parameters:
collection- the collection to queryqueryTerms- RQL terms that have been translated to use Property columnNames not jsonNames- Returns:
- A list of maps with keys as Property columnNames not jsonNames
- Throws:
ApiException
-
upsert
public final List<String> upsert(Collection collection, List<Map<String, Object>> records) throws ApiException- Throws:
ApiException
-
doUpsert
public List<String> doUpsert(Collection collection, List<Map<String, Object>> records) throws ApiExceptionUpserts the key/values pairs for each record into the underlying data source.Keys that are not supplied in the call but that exist in the row in the target DB should not be modified.
Each row should minimally contain key value pairs that satisfy one of the tables unique index constraints allowing an update to take place instead of an insert if the row already exists in the underlying data source.
IMPORTANT #1 - implementors should note that the keys on each record may be different.
IMPORTANT #2 - strict POST/PUT vs POST/PATCH semantics are implementation specific. For example, a RDBMS backed implementation may choose to upsert only the supplied client supplied keys effectively making this a POST/PATCH operation. A document store that is simply storing the supplied JSON may not be able to do partial updates elegantly and replace existing documents entirely rendering this a POST/PUT.
- Parameters:
collection- the collection being modifiedrecords- the records being modified- Returns:
- the encoded resource key for every supplied row
- Throws:
ApiException
-
patch
public List<String> patch(Collection collection, List<Map<String, Object>> records) throws ApiExceptionShould be called by Actions instead of upsert() only when all records are strictly known to exist.The default implementation simply calls upsert().
- Parameters:
collection- the collection to patchrecords- the key/value pairs to update on existing records- Returns:
- the keys of all resources modified
- Throws:
ApiException
-
doPatch
public List<String> doPatch(Collection collection, List<Map<String, Object>> rows) throws ApiException- Throws:
ApiException
-
delete
public final void delete(Collection collection, List<Map<String, Object>> indexValues) throws ApiExceptionDeletes rows identified by the unique index values from the underlying data source.IMPORTANT implementors should note that the keys on each
indexValuesrow may be different.The keys should have come from a unique index, meaning that the key/value pairs for each row should uniquely identify the row, however there is no guarantee that each row will reference the same index.
- Parameters:
collection- the collection being modifiedindexValues- the identifiers for the records to delete- Throws:
ApiException
-
doDelete
public void doDelete(Collection collection, List<Map<String, Object>> indexValues) throws ApiException- Throws:
ApiException
-
configApi
Does some final configuration adds all non excluded Collections to the Api via Api.withCollection- Parameters:
api- the Api to configure.
-
configDb
Subclasses should reflectively create Collections and Properties, Indexes, and Relationships here.The default implementation simply delegates to
buildCollections()andbuildRelationships().Generally, you will need to override buildCollections when implementing a new Db subclass but can probably leave buildRelationships alone as all it does is reflectively build Relationships off of Indexes that are on the Collections.
- Throws:
ApiException- when configuration fails
-
buildCollections
protected void buildCollections()Creates a collection for every table name inincludeTablesgiving the Collections and Properties beautified JSON names.Subclasses should override this method to reflectively create Collections, Properties and Indexes and then call super.buildCollections() if they want names them beautified.
-
buildRelationships
protected void buildRelationships()Creates ONE_TO_MANY, MANY_TO_ONE, and MANY_TO_MANY Relationships with attractive RESTish names based on the primary Index and foreign key Index structures of the Collections.For all foreign key indexes, two relationships objects are created representing both sides of the relationship. A MANY_TO_ONE also creates a ONE_TO_MANY and vice versa and there are always two for a MANY_TO_MANY modeling the relationship from both sides.
-
beautifyCollectionName
Attempts to camelCase the table name to make it an attractive REST collection name. IfincludeTablescontains tableName as a key, the value fromincludeTablesis returned as is.- Parameters:
tableName- the name of an underlying datasource table to be turned into a pretty REST collection name- Returns:
- a camelCased and pluralized version of tableName
- See Also:
-
beautifyName
Try to make an attractive camelCase valid javascript variable name.Lots of sql db designers use things like SNAKE_CASE_COLUMN_NAMES that look terrible as json property names.
- Parameters:
name- the to beautify- Returns:
- a camelCased version of
name - See Also:
-
makeRelationshipName
Attempts to construct a sensible json property name for a Relationship.For example, a "ONE Author TO_MANY Books" relationship might show up as "books" on the Author collection and "Author" on the Books collection.
The algorithm attempts to pluralize the "MANY" side of the relationship.
The "ONE" side of a relationship uses the first foreign key column name minus an trailing case insensitive "ID", so in the above example, if there is a foreign key Book.authorId pointing to the Author table, the the name would be "author". If the foreign key column name was Book.primaryAuthorKey, the relationship would be named "primaryAuthorKey".
- Parameters:
collection- the collection the relationship name being created will belong torelationship- the relationship- Returns:
- the json property name representing this Relationship.
-
castDbOutput
Casts value as Property.type- Parameters:
property- the property the value is assigned tovalue- the value pulled from the DB- Returns:
valuecast toProperty.type
-
castJsonInput
Casts value as Property.type.- Parameters:
property- the property the value is assigned tovalue- the value to cast to the datatype of property- Returns:
valuecast toProperty.type- See Also:
-
mapToJsonNames
-
mapToColumnNames
protected Set<io.inversion.rql.Term> mapToColumnNames(Collection collection, io.inversion.rql.Term term) -
mapToColumnNames
-
getProperty
-
getCollection
-
getCollectionByTableName
-
removeCollection
-
getCollections
- Returns:
- a shallow copy of
collections
-
withIncludeTables
Utility that parses a comma and pipe separated list of table name to collection name mappings.Example: db.tables=customers,books-prod-catalog|books,INVENTORY_ADJUSTED_BY_PERIOD|inventory
This method is primarily useful when an Api designer wants to manually configure Collections instead of having the Db reflectively build the Collections.
This method can also be called prior to the db reflection phase of startup to whitelist tables that the reflection should include.
The string is first split on "," to get a list of table names.
If the table name contains a "|" character, the part on the left is considered the tableName and the part on the right is considered the collectionName.
If there is no "|" then the beautified tableName is used for the collectionName.
- Parameters:
includeTables- underlying data source tables that should be included as REST collections- Returns:
- this
- See Also:
-
withIncludeTable
Whitelists tableName as an underlying table that should be reflectively bootstrapped and exposed as collectionName.- Parameters:
tableName- the table to build a Collection forcollectionName- the name of the target collection that can be different from tableName.- Returns:
- this
-
withCollections
- Parameters:
collections- to include (add not replace)- Returns:
- this
-
withCollection
-
filterOutJsonProperty
Checks if "collectionName.columnName" or just "columnName" is specifically included or excluded viaincludeColumnsexcludeColumnsor is a valid Property columnName.This can be used to filter out things like URL path mapped parameters that don't actually match to "columns" in a document store.
This does not prevent the underlying Property from being part of a Collection object model and the names here don't actually have to be Properties.
- Parameters:
collection- the collection in questionname- the name of the property to optionally filter- Returns:
- true if the property should be excluded
-
withIncludeColumns
-
withExcludeColumns
-
isType
-
getType
-
withType
-
isBootstrap
public boolean isBootstrap() -
withBootstrap
-
isDryRun
public boolean isDryRun() -
withDryRun
-