I'm using golang and gorm to talk to a MySQL database.
I have a table with release metadata:
type OSType string
const (
Windows OSType = "windows"
Mac OSType = "mac"
)
type AgentMetadata struct {
Version string `gorm:"primary_key"`
OS OSType `gorm:"primary_key" sql:"type:ENUM('windows','mac')"`
Name string `sql:"not null"`
Description string `sql:"not null"`
ReleaseNotesUrl string `sql:"not null"`
UpdateTime time.Time `sql:"DEFAULT:current_timestamp"`
}
The releases are identified by a composite key - the OS and Version (number).
I have another table which defines the default version that clients should download (by OS):
type GlobalDefault struct {
OS OSType `gorm:"primary_key" sql:"type:ENUM('windows','mac')"`
Version string
AgentMetadata AgentMetadata
UpdateTime time.Time `sql:"DEFAULT:current_timestamp"`
}
What I want is to define two foreign keys from GlobalDefault to AgentMetadata (the pair OS and Version) and I want to be able to query the GlobalDefault table by its key OS and to get back a data structure which already contains the full AgentMetadata.
After a very long time and reading lots of documentatin, SO questions and code samples I tried to do the following:
func (repository *AgentRepository)GetGlobalDefault(os OSType) (error, AgentMetadata) {
gd := GlobalDefault{ OS:os }
result := AgentMetadata{}
return repository.connection.Find(&gd).Related(&result, "OS", "Version").Error, result
}
This "worked" in the sense that it really got the result filled up with AgentMetadata. However, it was not the correct metadata.
In my test I added two metadata records and one default:
And when I called err, queryResult := ar.GetGlobalDefault(defaultAgent.OS) instead of getting the 1.2.3 version metadata, I got the 1.2.3.1 metadata.
Indeed, when I turned on the gorm logs I saw that it ran the query:
[2017-07-15 17:51:50] [276.74ms] SELECT * FROM global_defaults WHERE global_defaults.os = 'windows'
[2017-07-15 17:51:50] [276.55ms] SELECT * FROM agent_metadata WHERE (os = 'windows')
First, it ignored the fact that I have a composite key in the agent_metadata table, and second, instead of doing a single query with a join, it made two queries (which is really a waste of time).
Another thing that bothers me is that I had to explicitly specify the foreign key names, whereas according to the documentation it seems that specifiying them is not needed or at least can be achieved by adding a tag:
type GlobalDefault struct {
OS OSType `gorm:"primary_key" sql:"type:ENUM('windows','mac')"`
Version string
AgentMetadata AgentMetadata `gorm:"ForeignKey:OS;AssociationForeignKey:OS"`
UpdateTime time.Time `sql:"DEFAULT:current_timestamp"`
}
Here I only added a tag for the OS column, however, I tried concatenating the foreign key names and neither option seemed to have an effect on the result. Without explicitly specifying the foreign key names in the API, the related data would just not be read. Having the pass the names to the query means that my DB mapping is not consolidated in a single place and I don't like that.
Can my scenario be solved? Can I have a composite foreign key? Can I specify all ORM properties in a single place? How can I make gorm create foreign keys in the DB (I noticed that the schema is created without foreign keys from GlobalDefault to AgentMetadata)?
How can I make gorm create foreign keys in the DB?
here is a sample:
db.AutoMigrate(&model.User{}).
AddForeignKey("account_id", "accounts(id)", "CASCADE", "CASCADE").
AddForeignKey("role_id", "roles(id)", "RESTRICT", "RESTRICT")
Related
Environment: MySQL Server 8.0, .NET Core 3.1, MySql.Data 8.0.28, NHibernate 5.3.11
I have following table:
CREATE TABLE `Master` (
`Row_Id` char(36) NOT NULL DEFAULT (uuid()),
`Path` varchar(1000) NOT NULL,
PRIMARY KEY (`Row_Id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
Following is entity definition and mapping:
public class MasterEntity
{
public virtual Guid RowId { get; set; }
public virtual string Path { get; set; }
}
internal sealed class MasterMap : ClassMapping<MasterEntity>
{
public MasterMap()
{
Table("Master");
Id
(
x => x.RowId,
map =>
{
map.Column("Row_Id");
map.Generator(Generators.GuidComb);
}
);
Property(x => x.Path, map => { map.Column("Path"); map.NotNullable(true); map.Type(TypeFactory.GetAnsiStringType(1000)); });
}
}
Following is how I INSERT this entity using NHibernate:
using(ISession session = SessionFactory.OpenSession())
{
MasterEntity entity = new MasterEntity();
entity.Path = "c:\whatever";
session.Save(entity);
session.Flush();
}
This inserts the record correctly. Up to here, everything is fine.
Now, I change the definition of Row_Id column as below:
`Row_Id` binary(16) NOT NULL DEFAULT (uuid_to_bin(uuid(),1)),
I do not change anything in my C# code. Now, the session.Flush(); call throws below exception:
NHibernate.Exceptions.GenericADOException: could not execute batch command.[SQL: SQL not available]
---> MySql.Data.MySqlClient.MySqlException (0x80004005): Data too long for column 'Row_Id' at row 1
The error looks obvious. The Guid in C# is 32 and column length is 16.
What changes I need to make in my mapping or entity definition (or other part of code) to insert C# Guid into BINARY(16) DEFAULT (uuid_to_bin(uuid(),1)) column?
By default, MySql.Data will store a Guid as CHAR(36). You can use BINARY(16) instead by specifying Old Guids = True; in your connection string.
From Connector/NET 8.0 Connection Options Reference:
The back-end representation of a GUID type was changed from BINARY(16) to CHAR(36). This was done to allow developers to use the server function UUID() to populate a GUID table - UUID() generates a 36-character string. Developers of older applications can add 'Old Guids=true' to the connection string to use a GUID of data type BINARY(16).
The way suggested in accepted answer works; but it has a problem.
The code in the question uses uuid_to_bin(uuid(),1); the second swap parameter is set to 1. With this, the INSERT works great; but when you SELECT the row, you get entirely different UUID. This is because, database drivers do not know whether the UUID is swapped or not.
Better solution is to use MySqlConnector instead of Oracle's Connector/NET (MySql.Data.dll).
For ADO.NET:
Configure it as explained here.
For .NET Core 2.1 or later, call DbProviderFactories.RegisterFactory("MySqlConnector", MySqlConnectorFactory.Instance) during application startup. This will register MySqlConnector’s DbProviderFactory implementation in the central DbProviderFactories registry.
My observation is that, call to DbProviderFactories.RegisterFactory is not needed. It just works by adding reference of MySqlConnector.dll and removing reference of MySql.Data.dll.
With MySqlConnector, the OldGuids=True; setting is available but obsolete; avoid it.
Use GuidFormat=TimeSwapBinary16; for uuid_to_bin(uuid(),1) (swap parameter set to 1).
Other possible values are mentioned here:
Determines which column type (if any) should be read as a System.Guid. The options include:
Char36:
All CHAR(36) columns are read/written as a Guid using lowercase hex with hyphens, which matches UUID().
Char32:
All CHAR(32) columns are read/written as a Guid using lowercase hex without hyphens.
Binary16:
All BINARY(16) columns are read/written as a Guid using big-endian byte order, which matches UUID_TO_BIN(x).
TimeSwapBinary16:
All BINARY(16) columns are read/written as a Guid using big-endian byte order with time parts swapped, which matches UUID_TO_BIN(x,1).
LittleEndianBinary16:
All BINARY(16) columns are read/written as a Guid using little-endian byte order, i.e. the byte order used by Guid.ToByteArray() and the Guid(byte[]) constructor.
None:
No column types are automatically read as a Guid.
Default:
Same as Char36 if OldGuids=False; same as LittleEndianBinary16 if OldGuids=True.
For NHibernate:
Install NHibernate.MySqlConnector from nuget package.
Add configuration.DataBaseIntegration(c => c.MySqlConnectorDriver()); in Session Factory configuration.
Set GuidFormat in connection string as explained above.
For other ORMs:
Please refer to this for usage with other ORMs.
I'm new to Perl and DBIx::Class.
This is how I get my meaning_ids from the table translation where language = 5:
my $translations = $schema -> resultset('Translation')->search({ language => '5'});
After it I'm trying to push my data from the database into my array data:
while ( my $translation =$translations->next ) {
push #{ $data }, {
meaning_id => $translation-> meaning
};
}
$self->body(encode_json $data );
If I do it like this, I get the following error:
encountered object
'TranslationDB::Schema::Result::Language=HASH(0x9707158)', but neither
allow_blessed , convert_blessed nor allow_tags settings are enabled
(or TO_JSON/FREEZE method missing)
But if I do it like that:
while ( my $translation =$translations->next ) {
push #{ $data }, {
meaning_id => 0+ $translation-> meaning
};
}
$self->body(encode_json $data );
I don't get the error anymore, but the meaning is not the number out of the database. It's way too big (something like 17789000, but only numbers till 7000 are valid).
Is there an easy way to tell Perl that meaning_id is an INT and not a string?
It's a bit hard without knowing your schema classes, but #choroba is right. The error message says $translation->meaning is an instance of TranslationDB::Schema::Result::Language. That's explained in DBIx::Class::Manual::ResultClass on CPAN.
I believe there is a relationship to a table called meaning, and when you call $translation->meaning what you get is a new result class. Instead you need to call $translation->meaning_id. Actually that would only happen in a join, but your code doesn't look like it does that.
It seems $translation->meaning returns an object. Using 0+ just returns its address (that's why the numbers are so high).
It looks like there's a relationship between your translation and meaning tables. Probably, the translation table contains a foreign key to the meaning table. If you look in the Result class for your translation class then you will see that relationship defined - it will be called "meaning".
As you have that relationship, then DBIC has added a meaning method to your class which retrieves the meaning object that is associated with your translation.
But it appears that the foreign key column in your translation table is also called "meaning", so you expect calling the "meaning" method gives you the value of the foreign key rather than the associated object. Unfortunately it doesn't work like that. The relationship method overrides the column method.
This is a result of bad naming practices. I recommend that you call the primary key for every table id and the foreign key that links to another table <table_name>_id - so the column in your translation table would be called meaning_id. That way you can distinguish between the value of the key ($translation->meaning_id) and the associated meaning object ($translation->meaning).
A work-around you can use if you can't rename columns, is to use the get_column method - $translation->get_column('meaning').
I have a requirement wherein I have to delete an entry from the couchbase bucket. I use the delete method of the CouchbaseCient from my java application to which I pass the key. But in one particular case I dont have the entire key name but a part of it. So I thought that there would be a method that takes a matcher but I could not find one. Following is the actual key that is stored in the bucket
123_xyz_havefun
and the part of the key that I have is xyz. I am not sure whether this can be done. Can anyone help.
The DELETE operation of the Couchbase doesn't support neither wildcards, nor regular expressions. So you have to get the list of keys somehow and pass it to the function. For example, you might use Couchbase Views or maintain your own list of keys via APPEND command. Like create the key xyz and append to its value all the matching keys during application lifetime with flushing this key after real delete request
Well, I think you can achieve delete using wildcard or regex like expression.
Above answers basically says,
- Query the data from the Couchbase
- Iterate over resultset
- and fire delete for each key of your interest.
However, I believe: Delete on server should be delete on server, rather than requiring three steps as above.
In this regards, I think old fashioned RDBMS were better all you need to do is fire SQL query like 'DELETE * from database where something like "match%"'.
Fortunately, there is something similar to SQL is available in CouchBase called N1QL (pronounced nickle). I am not aware about JavaScript (and other language syntax) but this is how I did it in python.
Query to be used: DELETE from b where META(b).id LIKE "%"
layer_name_prefix = cb_layer_key + "|" + "%"
query = ""
try:
query = N1QLQuery('DELETE from `test-feature` b where META(b).id LIKE $1', layer_name_prefix)
cb.n1ql_query(query).execute()
except CouchbaseError, e:
logger.exception(e)
To achieve the same thing: alternate query could be as below if you are storing 'type' and/or other meta data like 'parent_id'.
DELETE from where type='Feature' and parent_id=8;
But I prefer to use first version of the query as it operates on key, and I believe Couchbase must have some internal indexes to operate/query faster on key (and other metadata).
Although it is true you cannot iterate over documents with a regex, you could create a new view and have your map function only emit keys that match your regex.
An (obviously contrived and awful regex) example map function could be:
function(doc, meta) {
if (meta.id.match(/_xyz_/)) {
emit(meta.id, null);
}
}
An alternative idea would be to extract that portion of the key from each document and then emit that. That would allow you to use the same index to match different documents by that particular key form.
function(doc, meta) {
var match = meta.id.match(/^.*_(...)_.*$/);
if (match) {
emit(match[1], null);
}
}
In your case, this would emit the key xyz (or the corresponding component from each key) for each document. You could then just use startkey and endkey to limit based on your criteria.
Lastly, there are a ton of options from the information retrieval research space for building text indexes that could apply here. I'll refer you to this doc on permuterm indexes to get you started.
I'm building a site in Laravel.
I have foreign key constraints set up among InnoDB tables.
My problem is that if i don't select a value in a, say, select box, the framework tries to insert or update a record in a table with '' (empty string). Which causes a MySQL error as it cannot find the equivalent foreign key value in the subtables.
Is there some elegant way to force the insertion of NULL in the foreign key fields other than checking out every single field? Or to force MySQL to accept '' as a "null" foreign key reference?
In other words: I have a, say, SELECT field with first OPTION blank. I leave the blank OPTION chosen. When I submit, an empty string '' is passed. In MySQL apparently I can do UPDATE table SET foreignKey=NULL but not UPDATE table SET foreignKey=''. It does not "convert" to NULL. I could check the fields one by one but and convert '' to NULL for every foreign key, maybe specifying all of them in an array, I was wondering if there's a more streamlined way to do this.
Maybe have to change my ON UPDATE action (which is not set) in my DB schema?
Edit: the columns DO accept the NULL value, the problem is in how the framework or MySQL handle the "empty value" coming from the HTML. I'm not suggesting MySQL "does it wrong", it is also logical, the problem is that you can't set a "NULL" value in HTML, and I would like to know if there's an elegant way to manage this problem in MySQL or Laravel.
In other words, do I have to specify manually the foreign keys and construct my query accordingly or is there another robust and elegant way?
My code so far for the model MyModel:
$obj = new MyModel;
$obj->fill(Input::all())); // can be all() or a subset of the request fields
$obj->save();
At least since v4 of Laravel (and Eloquent models), you can use mutators (aka setters) to check if a value is empty and transform it to null, and that logic is nicely put in the model :
class Anything extends \Eloquent {
// ...
public function setFooBarAttribute($value) {
$this->attributes['foo_bar'] = empty($value)?null:$value;
}
}
You can check out the doc on mutators.
I've been oriented by this github issue (not exactly related but still).
Instead of using
$obj = new MyModel;
$obj->fill(Input::all())); // can be all() or a subset of the request fields
$obj->save();
Use
$obj = new MyModel;
$obj->fieldName1 = Input::get('formField1');
$obj->fieldName2 = Input::has('formField2') && Input::get('formField2') == 'someValue' ? Input::get('formField2') : null;
// ...
$obj->save();
And make sure your database field accepts null values. Also, you can set a default value as null from the database/phpmyadmin.
You must remove the "not null" attribute from the field that maps your foreign key.
In the model add below function.
public function setFooBarAttribute($value)
{
$this->attributes['foo_bar'] = $value?:null;
}
I would like to use the combination of two foreign keys plus the datetime field as my combined primary key.
But I get a
Catchable Fatal Error: Object of class DateTime could not be converted
to string in
C:\development\xampp\htdocs\happyfaces\vendor\doctrine\orm\lib\Doctrine\ORM\UnitOfWork.php
line 1337
when I do so. As soon as I remove the id: true from my YML entity declaration everything works fine again.
What is the problem that occurs here? It seems to be rather a Symfony2 or a Doctrine2 bug to me, because the datetime is set fine in the database if I don't declare the datetime column to be part of the primary key.
Can anyone help or advise?
Its not possible and not recommended. For primary key focus on primitive data types such as Integer or String. The most RDMS System prefer Integer as primary key for maximum performance.
Take look: http://doctrine-orm.readthedocs.org/en/2.1/tutorials/composite-primary-keys.html
Maybe a workaround could work by adding a new Doctrine data type. With a __toString() function, but I think Doctrine will force you to use primitive data types only.
class Foo
{
private $bar = 'test';
public function __toString()
{
return $this->bar;
}
}
echo new Foo();
Your error means in general DateTime has no __toString() function or is not string compatible. I never tested it to use a custom data type as primary key. So you've to try it yourself.
Take a look: http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/types.html
Another try is use String as Primary key and set your id with
$entity->setId(new \DateTime()->format('yyyy/mm/dd'));
Here is a similar question: Symfony/Doctrine: DateTime as primary key