Steps to reproduce:
- Set up a Drupal DateTime field to map to a Salesforce DateTime field.
- Create/Update record with DateTime on Salesforce and execute Salesforce Pull cron.
Example Exception:
Drupal\Core\Database\DatabaseExceptionWrapper: SQLSTATE[22001]: String data, right truncated: 1406 Data too long for column 'field_date_value' at row 1: INSERT INTO {node__field_date} (entity_id, revision_id, bundle, delta, langcode, field_date_value) VALUES (:db_insert_placeholder_0, [error]
:db_insert_placeholder_1, :db_insert_placeholder_2, :db_insert_placeholder_3, :db_insert_placeholder_4, :db_insert_placeholder_5); Array
(
[:db_insert_placeholder_0] => 1
[:db_insert_placeholder_1] => 1
[:db_insert_placeholder_2] => salesforce_ticket
[:db_insert_placeholder_3] => 0
[:db_insert_placeholder_4] => en
[:db_insert_placeholder_5] => 2017-07-29T14:57:00.000+0000
)
in Drupal\Core\Entity\Sql\SqlContentEntityStorage->saveToDedicatedTables() (line 1289 of /Users/jessetolj/Documents/Projects/drupal8-sandbox/docroot/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php).
Probably need similar casting for Pull as in https://www.drupal.org/node/2856995
I can try to work on a patch for this if someone can give me some guidance on the best place to implement this logic. Drupal\salesforce_mapping\Entity\MappedObject::pull() doesn't seem like the right place.
Comments
Comment #2
aaronbaumanThanks for the bug report.
You're right, ::pull is not a good place for this.
Per the comment there:
Instead of what we're doing now, which is essentially setting the raw value directly on the drupal entity, we should delegate it to the mapping field plugin.
This would mean:
Let me know if that's enough to get you started.
Comment #3
jtolj commentedYes, makes sense. I'll try to get some time approved to work on it and make a patch. Thanks!
Comment #4
jtolj commentedHere's a first pass at this.
I'm not clear on whether RelatedProperties and RelatedIDs require an override for the pullValue() method (pushValue() is not overridden). I don't have data handy to test those with but can get something set up if it looks like the default implementation won't work.
I've tested property mapping for boolean, date, datetime, picklist, multipicklist, and SFID with this patch applied and all are pulling correctly for me.
Picklist and multipicklist obviously depend on matching option keys being set up on the Drupal end.
Thanks!
Comment #5
aaronbaumanTaking a look now.
Let's see what testbot says too.
These plugins probably need overrides in both directions.
I've been mostly working with Properties, so haven't given them the attention they deserve.
Comment #6
aaronbaumanLooks like you're headed in the right direction so far - thanks for the patch!
Maybe we should throw an exception here, rather than returning a value, so that the caller knows for sure that this value doesn't make sense.
Same here. Let's allow this exception to bubble up so that the caller doesn't think our transformation was successful.
Let's put this into a default case.
substr() doesn't make sense for the other cases of the switch statements.
In the meantime, I'll fix our broken test coverage...
Comment #7
jtolj commentedGreat, I'll make those changes tomorrow. Thanks for taking a look so quickly!
Comment #8
jtolj commentedUpdated patch attached. Thanks!
Comment #9
aaronbaumanComment #10
tauno commentedIt looks like SalesforceMappingFieldPluginBase::pullValue() is manipulating the values based on what the Salesforce field definitions are rather than what the Drupal field definitions are. Since the data is intended to end up in Drupal fields shouldn't we be looking at that? The most obvious example of an issue is the default case where the value from Salesforce is trimmed to the max length the Salesforce field defines - in other words nothing would happen.
Agreed that related entities and their properties will need an override to map the SF Id to a Drupal Id.
Comment #11
tauno commentedComment #12
tauno commentedUpdated patch to include RelatedIds override.
Comment #13
jtolj commented@tuano
Thanks for looking it over and implementing the RelatedIds override.
I definitely agree that the max length should be coming from the Drupal field. I'll make that change.
For the other field types, my thought was the goal should be transforming the data type coming from Salesforce into what Drupal might expect for a similar field. It would be up to the user to map them rationally.
For example, the multipicklist Salesforce type - we need knowledge here of how it's coming in from Salesforce (semicolon delimited) to restructure it into something that Drupal would expect.
Let me know your thoughts and I can make adjustments if needed.
Thanks!
Comment #14
aaronbaumanI think we don't need to handle this exception at all.
Just allow it to bubble up, and allow the caller to dispatch an event as applicable.
I'll just echo Tauno's point again, and add some specific detail.
We definitely need some manipulation based on both Drupal and Salesforce fields, e.g. for multipicklist and booleans.
For dates, let's not bother changing the format at this point.
If, for example, i have a SF date field mapped to a Drupal textfield, I don't need to do any manipulation.
If I'm mapping a SF datetime to a Drupal date, I need to strip off the "time" component.
These are fine I think, but we also need to ensure they make sense for Drupal.
For example, if a SF double is mapped to a Drupal integer, it needs to be re-cast.
Or, if multi-valued SF multi-picklist is mapped to a single-valued Drupal field, we need to reconcile that as well.
Thanks again for your work on this.
Comment #15
jtolj commentedWill do.
Okay, so essentially we would need conditional handling inside of each Salesforce type depending on the Drupal field type that it is mapped to. That makes sense in a lot of cases (like the above).
But for things like this:
My concern is that bi-directional sync would cause data loss. For example, let's say I have (double) 1.25 that I have mapped to an integer field in Drupal, and we cast it to (int) 1. Now when I re-save the node, and things are synced back to Salesforce it is re-cast to (double) 1.0 and I've lost data. Similarly, if I have a multipicklist mapped to a select that only allows one value, we could lose data going back to Salesforce.
Are we concerned about this at all, or is it just on the user to map these in a way that makes sense?
Thanks!
Comment #16
tauno commentedMissed a use statement in the patch in #12.
Comment #17
aaronbaumanYeah... this could get messy though, so let's not go too crazy.
IMO if there's anything that's not obvious to map, throw an exception and let the user sort it out.
Yeah, there's a lot of ways that folks could set up mappings that would break things badly.
There's only so much we can take responsibility for defending against though, and I'd say this example is out of scope.
Comment #18
jtolj commentedUpdated with changes requested (this includes tuano's patch in #16).
It looks to me from testing the Drupal handles casting the other cases on it's own. So if I map a float field in salesforce to an integer field in Drupal, it is cast correctly without any handling in this code (1.22 stores as 1 in Drupal). Similarly with multiselect, it will take the first x items of the array where x is the allowed number of values configured in field settings.
Thanks!
Comment #19
aaronbaumanComment #20
aaronbaumanA couple changes, biggest of which is to swap the hard-coded "value" with getMainPropertyName()
Also a missing "use" statement (should have been caught by tests - obviously we need more coverage).
Otherwise this is the same.
This is going to change when we update mapping field plugins to accommodate properties: #2899460: Handling of field properties
But I think it's probably fine to get this in now.
Comment #22
aaronbauman