How can I cause Grails/GORM to use default sequence values in postgres? - sql

When I define a domain object like:
class MusicPlayed {
String user
Date date = new Date()
String mood
static mapping = {
id name: 'played_id'
version false
}
}
I get a postgres sequence automatically defined like:
CREATE SEQUENCE seq_music_played
INCREMENT 1
MINVALUE 1
MAXVALUE 9223372036854775807
START 1
CACHE 1;
That's great -- but I'd love to have this become the default value for my id field. In other words, I'd like to have the table defined with:
played_id bigint DEFAULT nextval('seq_music_played'::regclass) NOT NULL,
... but this doesn't happen. So when my client code requires manual SQL invocation, I'm stuck pulling new values form the sequence instead of just relying on auto-population.
Is there any way to cause this table to be created "the way I want," or do I need to forgo gorm's table-creation magic and just create the tables myself with a db-creation script that runs at install-time?
Note My question is similar to How to set up an insert to a grails created file with next sequence number?, but I'm specifically looking for a solution that doesn't pollute my client code.

This works for me :-)
static mapping = {
id generator: 'native', params: [sequence: 'my_seq'], defaultValue: "nextval('my_seq')"
}
Generating something like:
create table author (
id int8 default nextval('nsl_global_seq') not null,...
for postgresql.

I would use:
static mapping = {
id generator: 'native', params:[sequence:'your_seq']
}
Additionally, i would update the DEFAULT-Value of the id-column via
ALTER TABLE your_table ALTER COLUMN id SET DEFAULT nextval('your_seq');
This is extremely useful for manual INSERTs
UPDATE - use liquibase for the default-column-problem:
changeSet(author:'Bosh', id:'your_table_seq_defaults', failOnError: true) {
sql ("ALTER TABLE your_table ALTER COLUMN id SET DEFAULT nextval('your_seq')")
}

I tend to create my tables directly in PostgreSQL and then map them in grails.
I took the best idea to the sequences-generated-IDs from here:
http://blog.wolfman.com/articles/2009/11/11/using-postgresql-with-grails
Give it a try and then smile at your former problems :)
rawi

You can define it in Config.groovy
grails.gorm.default.mapping = {
id generator: 'sequence'
}

Related

How to set fields non-nullable for JOOQ POJO

I have this DDL
CREATE TABLE user
(
id bigint NOT NULL PRIMARY KEY,
name string NOT NULL
);
org.jooq.codegen.KotlinGenerator generates such a class:
data class Users(
var id: Long? = null,
var name: String? = null,
): Serializable
I expect non-nullable fields to be non-nullable, like:
data class Users(
var id: Long,
var name: String,
): Serializable
I only use these settings:
generate.apply {
isRecords = true
isDaos = true
isPojosAsKotlinDataClasses = true
isSpringAnnotations = true
isPojos = true
}
Jooq ver. 3.15.10
How do I configure the generator so that the fields are non-nullable?
As of jOOQ 3.17, there are pending feature requests to add some additional convenience at the price of correctness to generated classes:
#10212 Add option to handle nullability in KotlinGenerator
#12934 Null-safe views of columns known to be non-null
The main reason why this hasn't been implemented yet is the many ways such non-nullability promises can break in the event of using operators like LEFT JOIN, UNION and many others, in case of which an expression that appears to be non-null will produce null values nonetheless.
While #10212 is very hard to get right, #12934 might be a compromise to be implemented soon, given that it affects only generated data class types, which can be used on a 1:1 basis by users who understand the tradeoffs.

Cannot fetch generated keys through JDBC on SQL Data Warehouse

I have a table like this in my Azure SQL Data Warehouse database:
CREATE TABLE t_identity (
id INTEGER IDENTITY (1, 1) NOT NULL,
val INTEGER
)
Now, using JDBC, I want to insert a row and fetch the generated identity value. This would work in SQL Server and most other databases:
try (Connection c = DriverManager.getConnection(url, properties);
Statement s = c.createStatement()) {
s.executeUpdate("insert into t_identity (val) values (1)",
Statement.RETURN_GENERATED_KEYS);
try (ResultSet rs = s.getGeneratedKeys()) {
while (rs.next())
System.out.println(rs.getObject(1));
}
}
But on SQL Data Warehouse, it doesn't work:
Exception in thread "main" com.microsoft.sqlserver.jdbc.SQLServerException: 'SCOPE_IDENTITY' is not a recognized built-in function name.
at com.microsoft.sqlserver.jdbc.SQLServerException.makeFromDatabaseError(SQLServerException.java:264)
at com.microsoft.sqlserver.jdbc.SQLServerStatement.getNextResult(SQLServerStatement.java:1585)
at com.microsoft.sqlserver.jdbc.SQLServerStatement.doExecuteStatement(SQLServerStatement.java:876)
at com.microsoft.sqlserver.jdbc.SQLServerStatement$StmtExecCmd.doExecute(SQLServerStatement.java:776)
at com.microsoft.sqlserver.jdbc.TDSCommand.execute(IOBuffer.java:7385)
at com.microsoft.sqlserver.jdbc.SQLServerConnection.executeCommand(SQLServerConnection.java:2750)
at com.microsoft.sqlserver.jdbc.SQLServerStatement.executeCommand(SQLServerStatement.java:235)
at com.microsoft.sqlserver.jdbc.SQLServerStatement.executeStatement(SQLServerStatement.java:210)
at com.microsoft.sqlserver.jdbc.SQLServerStatement.executeUpdate(SQLServerStatement.java:2060)
at SQLServer.main(SQLServer.java:65)
The other methods (e.g. executeUpdate(String, String[])) don't work either, as they delegate to the above one.
I understand that SQL Data Warehouse doesn't support the SCOPE_IDENTITY() and similar functions, which seem to be used behind the scenes by the mssql-jdbc driver:
<dependency>
<groupId>com.microsoft.sqlserver</groupId>
<artifactId>mssql-jdbc</artifactId>
<version>6.5.2.jre8-preview</version>
</dependency>
But is there a workaround?
Notes:
The output clause is also not available: https://learn.microsoft.com/en-us/sql/t-sql/queries/output-clause-transact-sql
I've also filed a bug on Github for mssql-jdbc
The workaround is to add to your insert query, then select the id.
insert into t_identity
(val)
select 'my value'
where not exists
(
select 1
from t_identity
where val = 'my value'
)
select id
from t_dan_identity
where val = 'my value'
Can you describe your business use case in more detail? If you're using JOOQ, I think you might have a mismatch in technologies.
Azure SQL Data Warehouse is not intended for transactional workloads. If JOOQ is just maintaining a few reference rows then I don't see a problem, but if it is your main data loading strategy you're not going to get a good result.
Here's some reading that might help:
Use cases and anti-patterns:
https://blogs.msdn.microsoft.com/sqlcat/2017/09/05/azure-sql-data-warehouse-workload-patterns-and-anti-patterns/
Solution models (scroll to diagrams): https://azure.microsoft.com/en-us/services/sql-data-warehouse/
Data loading best practices: https://learn.microsoft.com/en-us/azure/sql-data-warehouse/guidance-for-loading-data
Data loading patterns and strategies: https://blogs.msdn.microsoft.com/sqlcat/2017/05/17/azure-sql-data-warehouse-loading-patterns-and-strategies/

PostgreSQL - Rule to create a copy of the primaryID table

In my schema I want to have a primaryID and a SearchID. For every SearchID it is the primaryID plus some text at the start. I need this to look like this:
PrimaryID = 1
SearchID = Search1
Since the PrimaryID is set to autoincrement, I was hoping I could use a postgresql rule to do the following (pseudo code)
IF PRIMARYID CHANGES
{
SEARCHID = SEARCH(PRIMARYID)
}
This would hopefully occure exactly after the primaryID is updated and happen automatically. So, is this the best way of achieving this and can anyone provide an example of how it is done?
Thank you
Postgres 11 introduced genuine generated columns. See:
Computed / calculated / virtual / derived columns in PostgreSQL
For older (or any) versions, you could emulate a "virtual generated column" with a special function. Say your table is named tbl and the serial primary key is named tbl_id:
CREATE FUNCTION search_id(t tbl)
RETURNS text STABLE LANGUAGE SQL AS
$$
SELECT 'Search' || $1.tbl_id;
$$;
Then you can:
SELECT t.tbl_id, t.search_id FROM tbl t;
Table-qualification in t.search_id is needed in this case. Since search_id is not found as column of table tbl, Postgres looks for a function that takes tbl as argument next.
Effectively, t.search_id is just a syntax variant of search_id(t), but makes usage rather intuitive.

Using SQL for rails parameter/value

Let's say I have a very simple table that just has a single geometry column (point) that the user can update:
CREATE TABLE m_point (
id int(11) NOT NULL AUTO_INCREMENT,
point geometry NOT NULL,
deleted_at datetime DEFAULT NULL,
created_at datetime DEFAULT NULL,
updated_at datetime DEFAULT NULL,
PRIMARY KEY (id) )
How can I insert into this using rails, as the geometry column requires actual geometry?
I would have thought this would work:
loc = MPoint.new
loc.point = "POINT(#{params[:x]},#{params[:y]})"
loc.save!
but I get the error:
Mysql2::Error: Cannot get geometry object from data you send to the GEOMETRY field: INSERT INTO `m_point` (`point`) VALUES ('POINT(35, 10)')
as the POINT(X,Y) is seen as rails as being a string. How do I get it so that rails accepts POINT(#{params[:x]},#{params[:y]}) as an unquoted command?
Yes, it would be simple to do an INSERT, but I am wondering if there is any other way, short of installing a gem, to get this to work with rails.
One approach to do this would be to include a setter for the point attribute in your MPoint model, then execute manual SQL to update the column.
eg.
def set_point(x, y)
connection.execute("UPDATE m_point SET point = POINT(#{x}, #{y}) WHERE ID = #{self.id}")
end
You could further improve it by calling it on the after_save callback on the model, and use virtual attributes in your model to use in your set_point method.
I wouldn't expect this to work with vanilla Rails. The GEOMETRY type is specific to MySQL if I recall correctly, and only works with spatial extensions enabled.
You might look at activerecord-mysql2spatial-adapter

Bind a column default value to a function in SQL 2005

I have a column containing items that can be sorted by the user:
DOC_ID DOC_Order DOC_Name
1 1 aaa
2 3 bbb
3 2 ccc
I'm trying to figure out a way to properly initialize DOC_Order when the entry is created. A good value would either be the corresponding DO-CID (since it is autoassigned), or MAX(DOC-ORDER) + 1
After a bit of googling I saw it was possible to assign a scalar function's return to the default column.
CREATE FUNCTION [dbo].[NEWDOC_Order]
(
)
RETURNS int
AS
BEGIN
RETURN (SELECT MAX(DOC_ORDER) + 1 FROM DOC_Documents)
END
But each of my tries using MS SQL Management studio ended in a "Error validating the default for column 'DOC_Order'" message.
Any idea of what the exact SQL syntax to assign a function to DEFAULT is?
The syntax to add a default like that would be
alter table DOC_Order
add constraint
df_DOC_Order
default([dbo].[NEWDOC_Order]())
for DOC_Order
Also, you might want to alter your function to handle when DOC_Order is null
Create FUNCTION [dbo].[NEWDOC_Order]
(
)
RETURNS int
AS
BEGIN
RETURN (SELECT ISNULL(MAX(DOC_ORDER),0) + 1 FROM DOC_Documents)
END
IF someone wants to do it using the interface, typing
[dbo].[NEWDOC_Order]()
does the trick. You apparently need all brackets or it will reject your input.
Here's screen shots to do it through SQL Server Management Studio GUI:
Right click on table and select Design
Select DOC_Order column (or other column needing default) in the table's design view to see
properties
Update Default Value or Binding with function name with brackets
like so:
Note: as Luk stated, all brackets are needed including the schema (dbo in this case).