I'm trying to implement a trigger on Cassandra 3 to catch delete operations on a specific table by implementing
public Collection<Mutation> augment(Partition partition)
on ITrigger interface but I can't differentiate between update and delete operations.
How can I catch that the operation is a delete operation?
Here is how you can catch all type of deletion
public Collection<Mutation> augment(Partition partition) {
if(partition.partitionLevelDeletion().isLive()) {
UnfilteredRowIterator it = partition.unfilteredIterator();
while (it.hasNext()) {
Unfiltered unfiltered = it.next();
switch (unfiltered.kind()) {
case ROW:
Row row = (Row) unfiltered;
if (!row.deletion().isLive()) {
// Row deletion
}
for (Cell cell : row.cells()) {
if (cell.isTombstone()) {
// Cell deletion
} else {
// Insert or Update
}
}
break;
case RANGE_TOMBSTONE_MARKER:
// Range Deletion
break;
}
}
} else {
// Partition Level Deletion
}
}
Let's say we have the table :
CREATE TABLE kv (
pk int,
ck int,
d int,
PRIMARY KEY (pk, ck)
);
Here pk is the partition key and ck is the clustering key
Partition Level Deletion :
DELETE from kv WHERE pk = 1;
Range Deletion :
DELETE from kv WHERE pk = 1 AND ck > 10;
Row deletion :
DELETE from kv WHERE pk = 1 AND ck = 10;
And cell deletion :
DELETE d from kv WHERE pk = 1 AND ck = 10;
Related
I am trying to add a column of type array to my Postgres table using exposed.The goal is to have a statement like:
UPDATE posts
SET like_user_id = like_user_id || $1, likes = likes + 1
WHERE NOT (like_user_id #> $1)
AND pid = ($2)
posts table:
CREATE TABLE posts (
pid SERIAL PRIMARY KEY,
title VARCHAR(255),
body VARCHAR,
user_id INT REFERENCES users(uid),
author VARCHAR REFERENCES users(username),
date_created TIMESTAMP
like_user_id INT[] DEFAULT ARRAY[]::INT[],
likes INT DEFAULT 0
);
Kotlin Exposed framework does not have support for array of column type natively, you need to implement it yourself. Here's a generic version I found while trying to do the same thing https://github.com/LorittaBot/Loritta/blob/db577852a76266d207361b7d8257d24b4ee0b947/platforms/discord/legacy/src/main/java/com/mrpowergamerbr/loritta/utils/exposed/array.kt
fun <T> Table.array(name: String, columnType: ColumnType): Column<Array<T>> = registerColumn(name, ArrayColumnType(columnType))
class ArrayColumnType(private val type: ColumnType) : ColumnType() {
private fun supportsArrays() = !loritta.config.database.type.startsWith("SQLite")
override fun sqlType(): String = buildString {
if (!supportsArrays()) {
append("TEXT")
} else {
append(type.sqlType())
append(" ARRAY")
}
}
override fun valueToDB(value: Any?): Any? {
if (!supportsArrays())
return "'NOT SUPPORTED'"
if (value is Array<*>) {
val columnType = type.sqlType().split("(")[0]
val jdbcConnection = (TransactionManager.current().connection as JdbcConnectionImpl).connection
return jdbcConnection.createArrayOf(columnType, value)
} else {
return super.valueToDB(value)
}
}
override fun valueFromDB(value: Any): Any {
if (!supportsArrays()) {
val clazz = type::class
val clazzName = clazz.simpleName
if (clazzName == "LongColumnType")
return arrayOf<Long>()
if (clazzName == "TextColumnType")
return arrayOf<String>()
error("Unsupported Column Type")
}
if (value is java.sql.Array) {
return value.array
}
if (value is Array<*>) {
return value
}
error("Array does not support for this database")
}
override fun notNullValueToDB(value: Any): Any {
if (!supportsArrays())
return "'NOT SUPPORTED'"
if (value is Array<*>) {
if (value.isEmpty())
return "'{}'"
val columnType = type.sqlType().split("(")[0]
val jdbcConnection = (TransactionManager.current().connection as JdbcConnectionImpl).connection
return jdbcConnection.createArrayOf(columnType, value) ?: error("Can't create non null array for $value")
} else {
return super.notNullValueToDB(value)
}
}
}
I have this relationship for my tables. Activity, Workstation, Platform, Part are the lookup tables.
I have ActivitWorkstation that contains (ActivityId, WorkstationId) foreign keys to Activity and Workstation tables.
I also have PlatformPart that contains (PlatformId, PartId) foreign keys to Platform and Part tables.
Lastly I have PartStaging table that has (ActivityWorkstationId, PlatformPartId) foreign keys to ActivityWorkstation and PlatformPart tables.
Below is my PartStaging Edit method:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(PartStagingVM partstagingvm)
{
if (ModelState.IsValid)
{
PartStaging partstaging = new PartStaging();
var activityWorkstation = db.ActivityWorkstations.FirstOrDefault(aw => aw.ActivityId == partstagingvm.ActivityId && aw.WorkstationId == partstagingvm.WorkstationId);
if (activityWorkstation == null)
{
activityWorkstation = new ActivityWorkstation
{
ActivityId = partstagingvm.ActivityId,
WorkstationId = partstagingvm.WorkstationId
};
db.Entry(activityWorkstation).State = EntityState.Added;
}
var platformPart = db.PlatformParts.FirstOrDefault(pp => pp.PlatformId == partstagingvm.PlatformId && pp.PartId == partstagingvm.PartId);
if (platformPart == null)
{
platformPart = new PlatformPart
{
PlatformId = partstagingvm.PlatformId,
PartId = partstagingvm.PartId
};
db.Entry(platformPart).State = EntityState.Added;
}
var partStaging = db.PartStagings.FirstOrDefault(ps => ps.ActivityWorkstationId == activityWorkstation.Id && ps.PlatformPartId == platformPart.Id);
if (partStaging != null && partStaging.Id != partstagingvm.Id)
{
TempData["Message"] = "The record already exists.";
}
else
{
partstaging.Id = partstagingvm.Id;
partstaging.ActivityWorkstationId = activityWorkstation.Id;
partstaging.PlatformPartId = platformPart.Id;
partstaging.Description = partstagingvm.Description;
db.Entry(partstaging).State = EntityState.Modified;
db.SaveChanges();
TempData["Message"] = "The record has been modified.";
}
return RedirectToAction("Edit");
}
return View(partstagingvm);
}
I can edit ActivityWorkstation. I can edit PlatformPart. It prevents me from updating when the record already exists. But I have a problem when editing the Description. It's supposed to be the simplest update since all I have to do is to assign:
partstaging.Description = partstagingvm.Description
But when I submit, I get this error:
An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.InvalidOperationException: An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.
Source Error:
Line 216: partstaging.Description = partstagingvm.Description;
Line 217:
Line 218: db.Entry(partstaging).State = EntityState.Modified;
Line 219: db.SaveChanges();
Line 220:
What am I missing?
I am trying to create salesforce trigger on Lead that auto-populates a look up field which links the current Lead to an existing Account if there exist an Account with the same name as the Lead's Company custom field.
This is my code:
trigger Link_Lead_To_Account on Lead (before insert ) {
Set<String> whatIDs = new Set<String>();
MAP<id,String> accountMap= new MAP<id,String>();
// save the leads that have been triggered
for (Lead l : Trigger.new) {
whatIDs.add(l.id);
}
List<Lead> leads = [SELECT Id,Company FROM Lead where ID=:whatIDs ];
// loop through the triggered leads, if the account.name == to lead.company then link the found account to the lead
for (Integer i = 0; i <Trigger.new.size(); i++)
{
// System.Debug('++++++++++++++'+Trigger.new[i].company+Trigger.new[i].id);
if(accountMap.get(Trigger.new[i].company)!=null)
{
for(Account ac :[Select name,id from Account])
{
if(Trigger.new[i].Company==ac.Name)
{
Trigger.new[i].Account__c= ac.id;
break;
}
}
}
// System.Debug('Trigger.new[i].Account__c::::'+Trigger.new[i].Account__c);
// System.Debug('Trigger.new[i].company:::::'+Trigger.new[i].company);
// System.Debug('Trigger.new[i].ID:::::'+Trigger.new[i].ID);
}
update leads;
}
But it doesn't work at all. It throws the following error:
Review all error messages below to correct your data.
Apex trigger Link_Lead_To_Account caused an unexpected exception, contact your administrator: Link_Lead_To_Account: execution of AfterInsert caused by: System.StringException: Invalid id: TestAccount2: External entry point
As it requires the Company field to be an ID, but when I write an ID it does't perform any changes.
I managed to fix it.
This is the working class where newLeads.Values() is being populated in the constructor with to the Trigger.new() values on the before insert event:
public void LinkLeadToAccount() {
Set<String> companies = new Set<String>();
for (Lead l: newLeads.values()) {
if (l.Company != null) companies.add(l.Company);
}
if (companies.size() > 0) {
// Pick most recent Account where more than one with same name
Map<String, Id> accountNameToId = new Map<String, Id>();
for (Account a : [
select Name, Id
from Account
where Name in :companies
order by CreatedDate
]) {
accountNameToId.put(a.Name, a.Id);
}
if (accountNameToId.size() > 0) {
Lead[] updates = new Lead[] {};
for (Lead l: newLeads.values()) {
if (l.Company != null) {
Id accountId = accountNameToId.get(l.Company);
if (accountId != null) {
updates.add(new Lead(Id = l.Id, Account__c = accountId));
}
}
}
System.debug(' leads_to_update : ' + updates.size() + ' leads_to_update : ' + updates);
update updates;
}
}
}
I want my SaveChanges() function to update a record in my database and if the return value is '1' which is coming from my stored procedure then and only then 'delete' command (stored procedure) should not be executed.
Now, the problem is db.SaveChanges() (an instance of ObjectContext) is updating my record successfully but after updating, it executes the delete command. How should I tell my function that not to execute delete command.
using (var db = new PRLAdminEntities())
{
bool isExists = false;
string lastExisting = string.Empty;
string errorString = string.Empty;
db.Connection.Open();
trans = db.Connection.BeginTransaction();
//accounts to be sent back to client
var countriesToSendBack = new List<Polo.Common.Shared.Entities.Country>();
//process each account requiring database update
if (request.CountriesToUpdate != null)
{
foreach (var country in request.CountriesToUpdate)
{
//countriesToSendBack.Remove(country);
var temp = from row in db.Countries where row.Name.ToUpper() == country.Name.ToUpper() select row;
if (temp.Count<Polo.Common.Shared.Entities.Country>() > 0 && country.ChangeTracker.State == ObjectState.Added)
{
countriesToSendBack.Add(country);
db.Countries.ApplyChanges(country);
isExists = true;
lastExisting = country.Name;
errorString += country.Name + ", ";
//db.GetAllCountries();
//break;
continue;
}
if (country.ChangeTracker.State == ObjectState.Deleted)
{
db.DeleteObject(country);
}
//if a change or modification (not a delete)
if (country.ChangeTracker.State != ObjectState.Deleted)
{
//this account should be sent back
if (!countriesToSendBack.Contains((country)))
countriesToSendBack.Add(country);
if (country.Active == false)
{
db.Countries.ApplyCurrentValues(country);
}
}
//apply all changes
db.Countries.ApplyChanges(country);
}
if (isExists)
{
//response.Success = false;
//errorString.Replace(", " + lastExisting + ",", " & " + lastExisting);
//response.FaultMessage = "Duplicate Records";
}
}
//save all changes
int total = db.SaveChanges();
response.Success = true;
foreach (var countryItem in countriesToSendBack)
{
countryItem.Id = (from row in db.Countries where row.Name.ToUpper() == countryItem.Name.ToUpper() select row.Id).FirstOrDefault();
}
trans.Commit();
//refresh the account data which gets timestamp etc
db.Refresh(RefreshMode.StoreWins,countriesToSendBack);
//set the response values
response.Countries = countriesToSendBack;
}
}
Perhaps I misread your question, I do not totally get what you are trying to do.
But why not call SaveChanges() after the change and when all checks are positive perform a remove() and call savechanges() again?
There is no harm is calling SaveChanges() multiple times. It will mirror it's data to your database. If you perform a remove it will try to delete it in your database. That's the nice thing about it.. it does what you tell it to do ;-)
First off, thanks for any help. I am using ria services to insert/update/delete entities and save a history of those operations. I want to perform the operation and save the history in ONE call to the service. Right now I am stuck on the insert because I need the new entities ID that is generated on the insert. I might be taking the wrong approach all together (but I hope not). I have overriden the submit method, and am trying to save a snapshot in the history table, I don't want to save a snapshot of the original version:
public override bool Submit( ChangeSet changeSet )
{
//SUBMIT FIRST SO THE OBJECT(S) HAVE AN ID
var success = base.Submit( changeSet );
if ( success )
foreach ( var changeSetEntry in changeSet.ChangeSetEntries )
{
if ( changeSetEntry.Entity is MyBusinessEntity )
{
var newBusinessEntity = (MyBusinessEntity) changeSetEntry.Entity;
RecordModifiedMyBusinessEntity( changeSetEntry.Operation, newBusinessEntity );
}
}
return success;
}
private void RecordModifiedMyBusinessEntity( DomainOperation operation, MyBusinessEntity newBusinessEntity )
{
var hist = new BusinessEntityHistory
{
ChangedBy = new AuthenticationService().GetUser().FriendlyName,
ChangedDate = DateTime.Now,
Operation = operation.ToString(),
BusinessEntityId = newBusinessEntity.Id,
Group = newBusinessEntity.Group,
Priority = newBusinessEntity.Priority,
....
};
InsertBusinessEntityHistory( hist );
//HERE IS WHERE I WANT TO CALL SUBMIT CHANGES AGAIN, BUT 1 - IT'S NOT IN THE CHANGESET,
//AND 2 - THE OBJECT I ALREADY INSERTED IS IN THE CHANGESET (SO IF I SUBMIT AGAIN, IT GETS
//INSERTED TWICE AND NO HISTORY IS SAVED. AND 3 - I CAN'T DO THE HISTORY BEFORE BECAUSE I DON'T
//HAVE THE ID, AND I DON'T WANT TO DO A MAX ID + 1 BECAUSE SOMEONE ELSE MIGHT BE
//INSERTING INTO THE SAME TABLE
}
Here is the solution I ended up going with:
public override bool Submit( ChangeSet changeSet )
{
//submit the changes
var success = base.Submit( changeSet );
if ( success )
{
//make a new list of change set entries
var entries = new List<ChangeSetEntry>();
//each change set entry needs an id (not to be confused with the entity's id)
var maxId = 0;
//iterate through each change and add historical snapshot.
foreach ( var changeSetEntry in changeSet.ChangeSetEntries )
{
var entity = changeSetEntry.Entity;
var operation = changeSetEntry.Operation;
var myEntity = entity as MyEntityType;
if ( myEntity != null )
{
entries.Add( GetHistoryChangeSetEntry( ref maxId, operation, myEntity ) );
continue;
}
}
//make new change set with historical snapshots
var newChangeSet = new ChangeSet( entries );
//submit the new change set
base.Submit( newChangeSet );
}
return success;
}
private ChangeSetEntry GetHistoryChangeSetEntry( ref int maxId, DomainOperation operation, MyEntityType myEntity )
{
return new ChangeSetEntry
{
Id = ++maxId,
//We are inserting this change set entry
Operation = DomainOperation.Insert,
Entity = new MyEntityTypesHistory
{
ChangedBy = ServiceContext.User.Identity.Name,
ChangedDate = DateTime.Now,
//The operation performed on the original entity
Operation = operation.ToString(),
MyEntityId = myEntity.EntityId,
MyEntityField1 = myEntity.EntityField1,
MyEntityField2 = myEntity.EntityField2
}
};
}
I had to make a new change set, and new change set entries for it, and submit changes with the new change set.