I have a state which derives from LinearState and QueryableState. The state has a OneToMany mapping with one of the attributes in the sate. Adding a OneToMany marks the key with a Unique constraint.
When i try to consume and create a new such state (keeping linearId same), this Unique key is violated as corda stores all consumed & unconsumed states in the same table.
How can this be modelled in corda?
You could use JoinColumns with output_index and transaction_id as shown below. These values will always be different since any update will require a new transaction.
#JoinColumns({
#JoinColumn(name = "output_index", referencedColumnName = "output_index"),
#JoinColumn(name = "transaction_id", referencedColumnName = "transaction_id"),
})
Here is a blog explaining hierarchical relationships in Corda.
https://medium.com/corda/implementing-hierarchical-relationship-with-corda-queryablestate-15049fed5934
Related
The Corda vault API contains a class called CommonQueryCriteria which is implemented like so:
abstract class CommonQueryCriteria : QueryCriteria() {
abstract val status: Vault.StateStatus
open val relevancyStatus: Vault.RelevancyStatus = Vault.RelevancyStatus.ALL
open val constraintTypes: Set<Vault.ConstraintInfo.Type> = emptySet()
open val constraints: Set<Vault.ConstraintInfo> = emptySet()
open val participants: List<AbstractParty>? = null
abstract val contractStateTypes: Set<Class<out ContractState>>?
open val externalIds: List<UUID> = emptyList()
open val exactParticipants: List<AbstractParty>? = null
override fun visit(parser: IQueryCriteriaParser): Collection<Predicate> {
return parser.parseCriteria(this)
}
}
What is the purpose of the externalIds property?
Note: This surely can't be meant to map UniqueIdentifier.externalId because:
There is a type mismatch (UUID vs. String?).
LinearStateQueryCriteria exists to query states by linearId or externalId.
This was introduced to support querying of accounts back in Corda 4.3:
https://github.com/corda/accounts/blob/master/docs.md#querying-the-vault-by-account
The Vault Query documentation mentions this new attribute right at the bottom of the page when discussing owning keys:
https://docs.corda.net/docs/corda-os/4.8/api-vault-query.html#mapping-owning-keys-to-external-ids
Admittedly it is not very clear and should reference CommonQueryCriteria (not VaultQueryCriteria) and show an example.
Though, if we look at the CreateAccount flow provided by the account library, the AccountInfo is created with a UniqueIdentifier with only an id :
val newAccountInfo = AccountInfo(
name = name,
host = ourIdentity,
identifier = UniqueIdentifier(id = identifier)
)
while the constructor of the UniqueIdentifier has both externalId and id:
data class UniqueIdentifier
constructor(val externalId: String? = null, val id: UUID = UUID.randomUUID())
So, effectively, if it is true that externalId was introduced to support queries with account, the vault queries are actually using the UniqueIdentifier.id of AccountInfo and not its externalId (assuming that nobody is creating an AccountInfo manually overriding the CreateAccount() function, as I suspect).
In EFCore5, implicit tables are saved as Dictionary<TKey, object> sets, knows as Property Bag Entity Types. However, I cannot figure out how to create a LINQ query with a Where() clause that compiles for MySQL for such a property bag entity type.
For example, this code successfully retrieves the IQueryable reference to an intermediate table (generated by EFcore5's implicity many-to-many feature) given the ISkipNavigation:
ISkipNavigation nav = // some many-to-many relationship
IQueryable<Dictionary<string, object>> intermediateTable =
context.Set<Dictionary<string, object>>(nav.JoinEntityType.Name);
And this code successfully retrieves all the entries in the intermediate table:
List<Dictionary<string, object>> joins = await intermediateTable.ToListAsync();
In the resulting List, each Dictionary has just one key/value (representing the row).
However, I cannot figure out how to add a .Where() clause to the LINQ query which will compile:
joinTable.Where(d => d.Keys.First() == "foo").ToList();
joinTable.Where(d => d.Keys.Any(k => k == "foo")).ToList();
The error is:
Translation of member 'Keys' on entity type 'MachinePartMachineProfile (Dictionary<string, object>)' failed. This commonly occurs when the specified member is unmapped. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'
I do not wish to do client-side parsing for performance reasons (the join table will be to big).
I note that the type reported by the error is MachinePartMachineProfile (Dictionary<string, object>). Some investigation showed that these types are being generated based upon the static Model.DefaultPropertyBagType (which is a Dictionary<string, object>). But despite staring at the EFCore code base, I cannot discern how to correctly query such a default property bag type.
I am using MySQL as my database, if it is relevant.
You can index the dictionary directly, with knowledge of the column name.
Working example would be:
joinTable.Where(d => d[columnName] == "foo").ToList();
And for the sake of completeness, if you have an ISkipNavigation instance, you can infer these keys as follows:
string foreignKey = nav.ForeignKey.Properties.First().GetColumnBaseName();
string localKey = nav.Inverse.ForeignKey.Properties.First().GetColumnBaseName();
I have code as below
var ds = widget.datasource;
ds.item.InvoiceID_fk = 10;
ds.saveChanges();
InvoiceID_fk is a foreign key field.
It says "Foreign key usage is deprecated. Please Relation API instead."
How do we use Relation API for above code?
Thank you
I'm currently researching on Ignite, and I used web-console's automatic RDBMS integration feature for my MariaDB persistent store.
This made ignite configure a cache for one of my reference table which has a many-to-many relationship, with 2 fields, both primary-keys.
Example Structure in the persistent store:
CREATE TABLE `user_category` (
`USER_ID` bigint(20) NOT NULL,
`CATEGORY` bigint(20) NOT NULL,
PRIMARY KEY (`USER_ID`,`CATEGORY`),
KEY `FK48520EF2B4BDA303` (`USER_ID`),
KEY `FK48520EF2C941D634` (`CATEGORY`),
CONSTRAINT `FK48520EF2B4BDA303` FOREIGN KEY (`USER_ID`) REFERENCES `ctrl_app_user` (`USER_ID`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `FK48520EF2C941D634` FOREIGN KEY (`CATEGORY`) REFERENCES `request_category` (`CATEGORY_ID`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
This made web-console configure the cache like this:
ArrayList<QueryEntity> qryEntities = new ArrayList<>();
QueryEntity qryEntity = new QueryEntity();
qryEntity.setKeyType("model.UserCategoryKey");
qryEntity.setValueType("model.UserCategory");
qryEntity.setTableName("user_category");
HashSet<String> keyFields = new HashSet<>();
keyFields.add("userId");
keyFields.add("category");
qryEntity.setKeyFields(keyFields);
LinkedHashMap<String, String> fields = new LinkedHashMap<>();
fields.put("userId", "java.lang.Long");
fields.put("category", "java.lang.Long");
qryEntity.setFields(fields);
HashMap<String, String> aliases = new HashMap<>();
aliases.put("userId", "USER_ID");
qryEntity.setAliases(aliases);
ArrayList<QueryIndex> indexes = new ArrayList<>();
QueryIndex index = new QueryIndex();
index.setName("FK48520EF2B4BDA303");
index.setIndexType(QueryIndexType.SORTED);
LinkedHashMap<String, Boolean> indFlds = new LinkedHashMap<>();
indFlds.put("userId", false);
index.setFields(indFlds);
indexes.add(index);
index = new QueryIndex();
index.setName("FK48520EF2C941D634");
index.setIndexType(QueryIndexType.SORTED);
indFlds = new LinkedHashMap<>();
indFlds.put("category", false);
index.setFields(indFlds);
indexes.add(index);
qryEntity.setIndexes(indexes);
qryEntities.add(qryEntity);
ccfg.setQueryEntities(qryEntities);
return ccfg;
I am able to retrieve data from ignite properly using its standard SQL.
However, when trying to insert data to that cache, I am getting error 50000 which according to Ignite documentation, is a query that is unsupported by ANSI-99.
Documentation also mentioned to take a look into the SQLException message but the message only mentioned the error 50000.
sample insert statement:
insert into USER_CATEGORY (USER_ID, CATEGORY) values (1, 1);
Thanks in Advance.
Most likely you need to specify a schema name (cache name) for the query:
insert into "YourCacheName".USER_CATEGORY (USER_ID, CATEGORY) values (1, 1);
So, for everyone who will experience this issue in the future, I've resolved the issue by removing the query entity keys.
keyFields.add("userId");
keyFields.add("category");
Ignite treats keys in a reference table/cache as unique, so both columns needed to be unique, this is not applicable for reference tables with many-to-many relationship since this design is bound to have duplicates for each column.
Thanks for those who took a look at this issue!~
Do you have any example how to execute multiple transactions between participants A, B, C. I can do it ease between 2 parties A and B. Have fount the same discussion on the official Corda forum without result
Sharing transaction among multiple nodes
Is it possible? Do you have any example?
If you are issuing a new state, you can simply alter the participants of this state to ensure all parties receive the new state. Here is an example I have created with KYC data. This is example is updating a state (not issuing a new state), but the same principle applies - simply alter the participants list to control who will see that state. The KYC State is as follows:
#CordaSerializable
data class State(val firstName: String, val lastName: String, val accountNum: String, val owner: AbstractParty,
override val linearId: UniqueIdentifier = UniqueIdentifier(), val banksInvolved: List<AbstractParty> = emptyList()) : QueryableState, ContractState, LinearState {
//Each time a TD is issued with this KYC data, the bank it is issued to is added to this banks involved list, meaning the data is now stored in that banks vault
override val participants: List<AbstractParty> get() = banksInvolved.plus(owner)
This participants variable is the list of all nodes that will store this state in their vault (i.e will be notified about the states creation, update, etc). This is where you would add new nodes too for them to be able to view the state in their vault.
Then within a flow, to notify a new bank that they have received KYC data, we simply add the state as output with a new bank added to the banksInvolved list. This will make this new bank a participant with this state:
builder.addOutputState(KYCData.first().state.copy(data = KYCData.first().state.data.copy(
banksInvolved = KYCData.first().state.data.banksInvolved.plus(issuingInstitue)))) //Same state but with a new bank involved - i.e able to view this KYC data
check your state, there you can mention the List of participants for transaction.