How can I declare a Clustered key on a many-to-many relationship table in CodeFluent Entities with Sql Server Producer - codefluent

I have a CodeFluent Entities Model such as:
<cf:project defaultNamespace="S5T" xmlns:cf="http://www.softfluent.com/codefluent/2005/1" xmlns:cfx="http://www.softfluent.com/codefluent/modeler/2008/1" xmlns:cfmy="http://www.softfluent.com/codefluent/producers.mysql/2012/1" xmlns:cfom="http://www.softfluent.com/codefluent/producers.model/2005/1" xmlns:cfasp="http://www.softfluent.com/codefluent/producers.aspnet/2011/1" xmlns:cfaz="http://www.softfluent.com/codefluent/producers.sqlazure/2011/1" xmlns:cfps="http://www.softfluent.com/codefluent/producers.sqlserver/2005/1" defaultKeyPropertyTypeName="long" maxParameterNameLength="62" defaultConcurrencyMode="None" persistencePropertyNameFormat="{1}" defaultMethodAllowDynamicSort="false" defaultProducerProductionFlags="Default, Overwrite, RemoveDates" defaultMethodDistinct="false" createDefaultMethodForms="true" createDefaultApplication="false" createDefaultHints="false" productionFlags="Default, Overwrite, RemoveDates">
<cf:import path="Default.Surface.cfp" />
<cf:producer name="SQL Server" typeName="CodeFluent.Producers.SqlServer.SqlServerProducer, CodeFluent.Producers.SqlServer">
<cf:configuration produceViews="true" targetDirectory="..\Model7Bom\Persistence" connectionString="Server=MY-MACHINE\SQLEXPRESS;Database=model7;Integrated Security=true;Application Name=S5T;Password=MyPassword;User ID=MyUser" cfx:targetProject="..\Model7Bom\Model7Bom.vbproj" cfx:targetProjectLayout="Update, DontRemove" />
</cf:producer>
<cf:entity name="User" namespace="S5T">
<cf:property name="Id" key="true" cfps:hint="CLUSTERED" />
<cf:property name="Name" />
<cf:property name="Roles" typeName="{0}.RoleCollection" relationPropertyName="Users" />
</cf:entity>
<cf:entity name="Role" namespace="S5T">
<cf:property name="Id" key="true" cfps:hint="CLUSTERED" />
<cf:property name="Name" />
<cf:property name="Users" typeName="{0}.UserCollection" relationPropertyName="Roles" />
</cf:entity>
</cf:project>
I could sucessfully decorate the cf:property name="Id" on both entities with cfps:hint="CLUSTERED". This got me Sql Server producer to correctly output
CONSTRAINT [PK_Use_Id_Use] PRIMARY KEY CLUSTERED
CONSTRAINT [PK_Rol_Id_Rol] PRIMARY KEY CLUSTERED
as opposed to default NONCLUSTERED.
How can I accomplish that with the THIRD TABLE generated by the model, to accomodate the many to many relationship?
By default, the table creation generated snippet is such as:
CREATE TABLE [dbo].[Role_Users_User_Roles] (
[Id] [bigint] NOT NULL,
[Id2] [bigint] NOT NULL,
CONSTRAINT [PK_Roe_Id_Id2_Roe] PRIMARY KEY NONCLUSTERED
(
[Id],
[Id2]
) ON [PRIMARY]
)
However, if I decorate both properties with cfps:hint="CLUSTERED" as in:
cf:property name="Roles" typeName="{0}.RoleCollection" relationPropertyName="Users" cfps:hint="CLUSTERED" /
cf:property name="Users" typeName="{0}.UserCollection" relationPropertyName="Roles" cfps:hint="CLUSTERED" /
I get a snippet generated with PRIMARY KEY CLUSTERED for the PK in TABLE [dbo].[Role_Users_User_Roles], BUT, in addition, I get an UNDESIRED effect of having an incorrect script generated for adding relations (generated filename ...relations_add.sql), such as:
ALTER TABLE [dbo].[Role_Users_User_Roles] WITH NOCHECK ADD CONSTRAINT [FK_Roe_Id_Id_Rol] FOREIGN KEY (
[Id]
) REFERENCES [dbo].[Role](
[Id]
) CLUSTERED
Along with the error from Sql Server:
Error 3 SQL80001: Incorrect syntax near 'CLUSTERED'.
And CodeFluent Producer Error:
CodeFluentRuntimeDatabaseException: CF4116: Execution of file ...path..._relations_add.sql statement at line 2
I need all three PKs CLUSTERED in the three tables generated, but not the side effect of syntax error for generating the relations.

This is not supported out-of-the-box. The hint declared on the a relation is rarely used, more meant as a Foreign Key hint than a column hint. There are several options you can use to do this.
The easiest is to use a post-generation .SQL script to add the clustered setting manually. This is described here: How to: Execute custom T-SQL scripts with the Microsoft SQL Server producer.
You could also use the Patch Producer to remove the CLUSTERED word from the file once it has been created : Patch Producer
Otherwise, here is another solution that involves an aspect I've written as a sample here. You can save the following piece of XML as a file, and reference it as an aspect in your model.
This aspect will add the CLUSTERED hint to primary keys of all Many To Many tables inferred from entities that have CLUSTERED keys. It will add the hint before the table scripts are created and ran, and will remove it after (so it won't end up in the relations_add script).
<cf:project xmlns:cf="http://www.softfluent.com/codefluent/2005/1">
<!-- assembly references -->
<?code #reference name="CodeFluent.Producers.SqlServer.dll" ?>
<?code #reference name="CodeFluent.Runtime.Database.dll" ?>
<!-- namespace includes -->
<?code #namespace name="System" ?>
<?code #namespace name="System.Collections.Generic" ?>
<?code #namespace name="CodeFluent.Model.Code" ?>
<?code #namespace name="CodeFluent.Model.Persistence" ?>
<?code #namespace name="CodeFluent.Model.Code" ?>
<!-- add global code to listen to inference steps -->
<?code
Project.StepChanging += (sender1, e1) =>
{
if (e1.Step == ImportStep.End) // hook before production begins (end of inference pipeline)
{
var modifiedTables = ProjectHandler.AddClusteredHint(Project);
// get sql server producer and hook on production events
var sqlProducer = Project.Producers.GetProducerInstance<CodeFluent.Producers.SqlServer.SqlServerProducer>();
sqlProducer.Production += (sender, e) =>
{
// determine what SQL file has been created
// we want to remove hints once the table_diffs has been created, before relations_add is created
string script = e.GetDictionaryValue("filetype", null);
if (script == "TablesDiffsScript")
{
ProjectHandler.RemoveClusteredHint(modifiedTables);
}
};
}
};
?>
<!-- add member code to handle inference modification -->
<?code #member
public class ProjectHandler
{
public static IList<Table> AddClusteredHint(Project project)
{
var list = new List<Table>();
foreach (var table in project.Database.Tables)
{
// we're only interested by tables inferred from M:M relations
if (table.Relation == null || table.Relation.RelationType != RelationType.ManyToMany)
continue;
// check this table definition is ok for us
if (table.RelationKeyColumns.Count < 1 || table.RelationRelatedKeyColumns.Count < 1)
continue;
// check clustered is declared on both sides
string keyHint = GetSqlServerProducerHint(table.RelationKeyColumns[0].Property);
string relatedKeyHint = GetSqlServerProducerHint(table.RelationKeyColumns[0].Property);
if (keyHint.IndexOf("clustered", StringComparison.OrdinalIgnoreCase) < 0 ||
relatedKeyHint.IndexOf("clustered", StringComparison.OrdinalIgnoreCase) < 0)
continue;
// force hint now, we only need to do this on one of the keys, not all
table.PrimaryKey.Elements[0].SetAttribute("hint", CodeFluent.Producers.SqlServer.Constants.SqlServerProducerNamespaceUri, "clustered");
// remember this table
list.Add(table);
}
return list;
}
public static void RemoveClusteredHint(IEnumerable<Table> list)
{
foreach (var table in list)
{
table.PrimaryKey.Elements[0].RemoveAttribute("hint", CodeFluent.Producers.SqlServer.Constants.SqlServerProducerNamespaceUri);
}
}
// helper method to read XML element's hint attribute in the SQL Server Producer namespace
private static string GetSqlServerProducerHint(Node node)
{
if (node == null)
return null;
return node.GetAttributeValue<string>("hint", CodeFluent.Producers.SqlServer.Constants.SqlServerProducerNamespaceUri, null);
}
}
?>
</cf:project>

Related

Insert entity in NestJS / TypeOrm / Postgres

Here is my entity :
import { BaseEntity, Column, Entity, JoinColumn, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';
import { Client } from './client.entity';
#Entity({ name: 'directory_dir' })
export class Directory extends BaseEntity {
#PrimaryGeneratedColumn("uuid", { name: 'dir_id' })
id: string;
#Column( { name: 'dir_name' } )
name: string;
#JoinColumn({ name: 'cli_client_id'})
#ManyToOne(() => Client, { eager: true })
client: Client;
}
The table is created via liquibase :
<changeSet id="3" author="Me">
<createTable tableName="DIRECTORY_DIR">
<column name="DIR_ID" type="uuid">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="DIR_NAME" type="varchar(64)">
<constraints nullable="false"/>
</column>
<column name="CLI_CLIENT_ID" type="uuid">
<constraints nullable="false" foreignKeyName="FK_DIRECTORY_DIR_CLI_CLIENT_ID" references="CLIENT_CLI(CLI_ID)"/>
</column>
</createTable>
</changeSet>
I struggle to have it inserted into the database. I have tried several syntaxes but every time, I get an error because the id is not auto-generated:
null value in column "dir_id" of relation "directory_dir" violates not-null constraint
I tried this :
const directory = new Directory();
/* Also tried this one
* const directory = this.directoryRepository.create({
* name: makeDirRequest.name,
* client: user.client
* });
*/
directory.name = makeDirRequest.name;
directory.client = user.client;
this.logger.log('directory', JSON.stringify(directory));
// Also tried with insert instead of save
return await this.directoryRepository.save(directory);
In the logs, I can see that there is no dir_id but, in the way I understand it, it should not be a problem, as I expect TypeOrm to do it when generating the INSERT statement.
When I insert entities in the database with SQL statements, I can easily find them from typeORM so the typeORM configuration seems ok.
The problem came from the definition of the table in PostgreSQL. A default value must be supplied in order to explain how to auto-generate the primary key.
Declaring the column like this in Liquibase does the job :
<column name="DIR_ID" type="uuid" defaultValue="gen_random_uuid()">
<constraints primaryKey="true" nullable="false"/>
</column>
I just added defaultValue="gen_random_uuid()".
gen_random_uuid() is the new uuid_generate_v4(), available from Postgres 13
From this point, I could just create the entity and insert it like this :
return await Directory.create({
name: makeDirRequest.name,
client: user.client
}).save();

what is the xml mapping for json like this?

Tables:
Create table development(
developmentName varchar(100) primary key,
description varchar(100)
);
Create table Student(
name varchar(100) primary key,
dateType varchar(100),
developmentName foreign key reference development(developmentName)
);
Input Json:
The json would come in the format
{ "DevelopmentName":"comp", "description":"Descrizione Tracciatro 1", "Student":[ {
"name":"String",
"dateType":"String",
"DevelopmentName":"comp"
},
{
"name":"String",
"date_type":"String"
"DevelopmentName":"comp"
}]}
DevelopmentName is primary key and which is foreign key for Student.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="it.eng.billing.mapper.Development">
<resultMap id="BaseResultMap" type="it.eng.billing.model.Development">
<result column="DevelopmentName" jdbcType="VARCHAR" property="DevelopmentName" />
<result column="description" jdbcType="VARCHAR" property="description" />
</resultMap>
<insert id="insert" parameterType="it.eng.billing.model.Development" >
insert into Development (DevelopmentName, description) values (#{DevelopmentName,jdbcType=VARCHAR},#{description,jdbcType=VARCHAR})
</insert>
<insert id="insertOptions" parameterType="it.eng.billing.model.Student">
INSERT INTO Student (name,DevelopmentName, datatype)
VALUES
<foreach collection="list" item="option" open="(" separator="),(" close=")" >
#{option.name},#{option.DevelopmentName},#{option.datatype}
</foreach>
</insert>
</mapper>
There is problem with the XML. The json is not getting saved into database.
public interface DevelopmentMapper{
default void save(DevelopmentDto record) {
insert(record);
insertOptions(record.getStudent());
}
from service class, calling developmentMapper.save(DevelopmentDto obj)

AutocompleteInput suggestions are not working

I'm working on a project in react-admin and am trying to use an AutocompleteInputs within ReferenceInputs for Filter in a List. The list is a product that has a relationship with two different users, a consumer and provider. I'm currently able to obtain the choices for consumers and providers and filter my product list by either.
However, my issues arises with the autosuggestion. It's simply not working. Typing in the AutocompleteInput will not filter the choices to select from for either consumers or providers. I have another AutocompleteArrayInput within an ArrayReferenceInput for providers that's setup similarly and the autosuggestion is working perfectly fine.
Any thoughts?
Here's the broken AutocompleteInput:
export const ProductFilter = props => (
<Filter {...props}>
<ReferenceInput label="Consumer" reference="consumers"
source="consumer_user_id" allowEmpty>
<AutocompleteInput source="id" optionText={FullNameRenderer} />
</ReferenceInput>
<ReferenceInput label="Provider" reference="providers"
source="provider_user_id" alwaysOn allowEmpty>
<AutocompleteInput optionText={FullNameRenderer} />
</ReferenceInput>
<DateRangeInput alwaysOn />
</Filter>
)
And this is the working AutocompleteArrayInput:
export const ConsumerEdit = props => (
<Edit {...props}>
<TabbedForm redirect="show">
<FormTab label="Providers" path="provider_assignmenmts">
<ReferenceArrayInput label='Assigned Providers'
reference='providers' defaultValue={[]}
source="provider_ids" allowEmpty>
<AutocompleteArrayInput optionText={FullNameRenderer}/>
</ReferenceArrayInput>
</FormTab>
</TabbedForm>
</Edit>
)
In the component <AutocompleteInput source = "id" optionText = {FullNameRenderer} /> the attribute source = "id" is superfluous.
Well, it looks like this one is a larger issue within react-admin.
https://github.com/marmelab/react-admin/issues/3098

MyBatis map-underscore-to-camel-case not working

I am using MyBatis 2.0.0 and have problem: I have a userId and status field that need to retrieve data so I'm using Mybatis for it.
but when I try to get the data the MyBatis don't work and I keep getting the same error.
I've tried to add the lines to application.properties
#mybatis entity scan packages
mybatis.type-aliases-package=com.cnoga.**.dao
#Mapper.xml location
mybatis.mapper-locations=classpath*:/mybatis/**/*.xml
mybatis.configuration.map-underscore-to-camel-case=true
but nothing happend and I still get my error.
I even tried to create the mybatis-config.xml
like this:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="mapUnderscoreToCamelCase" value="true" />
</settings>
</configuration>
and add this line to application.properties instead the lines above:
"mybatis.config-location=classpath:/mybatis-config.xml"
The mapper file:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.proj.user.dao.UserMapper" >
<select id="query4Regist" resultType="java.util.Map" parameterType="java.util.Map" >
select u.user_id userId,
status
from t_user u
left join t_user_group ug on u.user_id = ug.user_id
where u.is_del = 1
and u.status between 0 and 4
and (
u.email = #{account,jdbcType=NVARCHAR}
<if test="countryCode != null and countryCode != ''">
or (u.mobile = #{account,jdbcType=NVARCHAR} and u.country_code = #{countryCode,jdbcType=NVARCHAR})
</if>
)
and (u.region_name = #{regionName} or ug.group_id = 7 or ug.group_id = 8)
order by u.user_id desc offset 0 rows fetch next 1 rows only
</select>
</mapper>
I solved the problem!
The problem was using the map functions.
To use MYBATIS I built struct only for the values ​​I wanted to transfer and I just changed in all the places where I called the map functions to get data from the new struct

Extending Modx modResource schema errors

I'm trying to extend the modx modresource object, but keep getting errors & I can't seem to figure out why. It is related to the schema (I think) but everything looks correct.
Schema:
<?xml version="1.0" encoding="UTF-8"?>
<model package="extresource" baseClass="xPDOObject" platform="mysql" defaultEngine="MyISAM" tablePrefix="modx_" version="1.0.0">
<object class="extResource" extends="modResource">
<composite alias="ResourceData" class="ResourceData" local="id" foreign="internalKey" cardinality="one" owner="local"/>
</object>
<object class="ResourceData" table="resource_data" extends="xPDOSimpleObject">
<field key="internalKey" dbtype="int" precision="11" phptype="integer" null="false" attributes="unsigned"/>
<field key="views" dbtype="int" precision="11" phptype="integer" null="true" />
<field key="starred" dbtype="int" precision="10" phptype="integer" null="false" />
<index alias="internalKey" name="internalKey" primary="false" unique="true" type="BTREE" >
<column key="internalKey" length="" collation="A" null="false" />
</index>
<aggregate alias="Resource" class="modResource" local="internalKey" foreign="id" cardinality="one" owner="foreign"/>
</object>
</model>
I'm testing it using:
$resource = $modx->getObject('modResource', 11112);
echo $resource->get('pagetitle'); //test I have the resource
$data = $resource->getOne('ResourceData');
The errors I get are:
Could not getOne: foreign key definition for alias ResourceData not
found. No foreign key definition for parentClass: modDocument using
relation alias: ResourceData
The table exists & has data, the package is registered in the modx extension packages. I've been over the schema many times & it looks right.
What is causing these errors?
You have to use the right object class in $modx->getObject. Otherwise you will get a modResource object, that does not know the extended object data and relationship.
$resource = $modx->getObject('extResource', 11112);
Does the resource you are loading have its class_key field set to extResource? That's needed for it to load the right resource object class.