Extract Drupal encoded data using MySQL from Ubercart table - sql

I am writing an integration piece between Drupal/Ubercart and a in-house admin system.
The customer uses Ubercart products with attributes (e.g. a Plaque, which can contain a name, a company, a registration date and month). When an order is placed, the values entered for the attributes are written to uc_order_products, where the data field contains the actual values entered by the user. As far as I can tell, this is the only place where product attributes values, as entered by an end user to place an order, is stored.
The encoded attribute values uses a format seen all over Drupal tables to encode objects:
a:3:{s:10:"attributes";a:4:{s:26:"Name (to appear on plaque)";a:1:{i:0;s:10:"Some
Namee";}s:7:"Company";a:1:{i:0;s:28:"Some Company Name Goes here_";}s:19:"Certification
Month";a:1:{i:0;s:0:"";}s:18:"Certification Year";a:1:
{i:0;s:4:"2011";}}s:9:"shippable";s:1:"1";s:6:"module";s:10:"uc_product";}
And expanded, it looks like this:
a:3:
{
s:10:"attributes";
a:4:
{
s:26:"Name (to appear on plaque)";
a:1:
{
i:0;
s:10:"Some Namee";
}
s:7:"Company";
a:1:
{
i:0;
s:28:"Some Company Name Goes Herep";
}
s:19:"Certification Month";
a:1:
{
i:0;
s:0:"";
}
s:18:"Certification Year";
a:1:
{
i:0;
s:4:"2011";
}
}
s:9:"shippable";
s:1:"1";
s:6:"module";
s:10:"uc_product";
}
I there a simple way to get to the individual field values within this text using SQL? I can write a SQL function to go look for specifics, but I'd like to know if someone knows of an existing MySQL approach (perhaps within Drupal) which would do this.
Thanks!

That's a serialized array, meaning data that was processed by PHP's serialize() function before Drupal inserted it into the database. Use the opposite function unserialize() to turn that string back into an array.

Don't know if any built-in solution exists, but I ended up writing a SQL Function which takes as parameter text such as Name (to appear on plaque). The function then locates the text and extracts the succeeding { ... } block and from it retrieves the corresponding string value. Rough, but works in this case.
If someone has a better solution, I'd like to hear about it!

Related

How to achieve generic Audit.NET json data processing?

I am using Audit.Net library to log EntityFramework actions into a database (currently everything into one AuditEventLogs table, where the JsonData column stores the data in the following Json format:
{
"EventType":"MyDbContext:test_database",
"StartDate":"2021-06-24T12:11:59.4578873Z",
"EndDate":"2021-06-24T12:11:59.4862278Z",
"Duration":28,
"EntityFrameworkEvent":{
"Database":"test_database",
"Entries":[
{
"Table":"Offices",
"Name":"Office",
"Action":"Update",
"PrimaryKey":{
"Id":"40b5egc7-46ca-429b-86cb-3b0781d360c8"
},
"Changes":[
{
"ColumnName":"Address",
"OriginalValue":"test_address",
"NewValue":"test_address"
},
{
"ColumnName":"Contact",
"OriginalValue":"test_contact",
"NewValue":"test_contact"
},
{
"ColumnName":"Email",
"OriginalValue":"test_email",
"NewValue":"test_email2"
},
{
"ColumnName":"Name",
"OriginalValue":"test_name",
"NewValue":"test_name"
},
{
"ColumnName":"OfficeSector",
"OriginalValue":1,
"NewValue":1
},
{
"ColumnName":"PhoneNumber",
"OriginalValue":"test_phoneNumber",
"NewValue":"test_phoneNumber"
}
],
"ColumnValues":{
"Id":"40b5egc7-46ca-429b-86cb-3b0781d360c8",
"Address":"test_address",
"Contact":"test_contact",
"Email":"test_email2",
"Name":"test_name",
"OfficeSector":1,
"PhoneNumber":"test_phoneNumber"
},
"Valid":true
}
],
"Result":1,
"Success":true
}
}
Me and my team has a main aspect to achieve:
Being able to create a search page where administrators are able to tell
who changed
what did they change
when did the change happen
They can give a time period, to reduce the number of audit records, and the interesting part comes here:
There should be an input text field which should let them search in the values of the "ColumnValues" section.
The problems I encountered:
Even if I map the Json structure into relational rows, I am unable to search in every column, with keeping the genericity.
If I don't map, I could search in the Json string with LIKE mssql function but on the order of a few 100,000 records it takes an eternity for the query to finish so it is probably not the way.
Keeping the genericity would be important, so we don't need to modify the audit search page every time when we create or modify a new entity.
I only know MSSQL, but is it possible that storing the audit logs in a document oriented database like cosmosDB (or anything else, it was just an example) would solve my problem? Or can I reach the desired behaviour using relational database like MSSQL?
Looks like you're asking for an opinion, in that case I would strongly recommend a document oriented DB.
CosmosDB could be a great option since it supports SQL queries.
There is an extension to log to CosmosDB from Audit.NET: Audit.AzureCosmos
A sample query:
SELECT c.EventType, e.Table, e.Action, ch.ColumnName, ch.OriginalValue, ch.NewValue
FROM c
JOIN e IN c.EntityFrameworkEvent.Entries
JOIN ch IN e.Changes
WHERE ch.ColumnName = "Address" AND ch.OriginalValue = "test_address"
Here is a nice post with lot of examples of complex SQL queries on CosmosDB

SharePoint change column id for REST requests

I recently started experimenting with the REST API for SharePoint 2013 Foundation and I am trying to return all entries in a list. My GET request returns the data I am looking for, but the IDs used to identify the columns in the list are not helpful for identifying what the information is (see images below). The column IDs between 'Title' and 'ID', in the second image, are a jumble of characters.
SharePoint List View
Response Data
Is there any way to configure the list to use the column names as IDs? Also, is there some significance to the characters currently used as IDs?
You will need to make a second request to get a listing of columns that includes the InternalName and the Title which is what you are trying to reference:
You can use this REST call:
_api/web/lists/GetByTitle('Project Details')/fields
or you can use CSOM:
using (ClientContext context = new ClientContext(url))
{
List list = context.Web.Lists.GetByTitle("Project Details");
context.Load(list, l => l.Fields);
context.ExecuteQuery();
foreach(Field field in list.Fields)
{
Console.WriteLine(field.Title);
Console.WriteLine(field.InternalName);
}
}
SharePoint automatically generates the InternalName and it is a read-only field, at least using REST. It'll be easier to get the Field Data to correlate the InternalName to the Title than changing the values.
The column you are referring to, between Title and Id, is the ID of the content type associated to the item. It is not a column ID.
The SharePoint REST API is OData compliant, so you can use the $select parameter to query for the neccesary fields.
http://server/site/_api/web/lists('guid')/items?$select=Column1,Column2
Please be aware though, lookup fields need to be expanded as well, otherwise you get only the Id of the lookup item.
http://server/site/_api/web/lists('guid')/items?$select=LookupColumn&$expand=LookupColumn/Title

How do I implement, for instance, "group membership" many-to-many in Parse.com REST Cloud Code?

A user can create groups
A group had to have created by a user
A user can belong to multiple groups
A group can have multiple users
I have something like the following:
Parse.Cloud.afterSave('Group', function(request) {
var creator = request.user;
var group = request.object;
var wasGroupCreated = group.existed;
if(wasGroupCreated) {
var hasCreatedRelation = creator.relation('hasCreated');
hasCreatedRelation.add(group);
var isAMemberOfRelation = creator.relation('isMemberOf');
isAMemberOfRelation.add(group);
creator.save();
}
});
Now when I GET user/me with include=isMemberOf,hasCreated, it returns me the user object but with the following:
hasCreated: {
__type: "Relation"
className: "Group"
},
isMemberOf: {
__type: "Relation"
className: "Group"
}
I'd like to have the group objects included in say, 'hasCreated' and 'isMemberOf' arrays. How do I pull that using the REST API?
More in general though, am I approaching this the right way? Thoughts? Help is much appreciated!
First off, existed is a function that returns true or false (in your case the wasGroupCreated variable is always going to be a reference to the function and will tis always evaluate to true). It probably isn't going to return what you expect anyway if you were using it correctly.
I think what you want is the isNew() function, though I would test if this works in the Parse.Cloud.afterSave() method as I haven't tried it there.
As for the second part of your question, you seem to want to use your Relations like Arrays. If you used an array instead (and the size was small enough), then you could just include the Group objects in the query (add include parameter set to isMemberOf for example in your REST query).
If you do want to stick to Relations, realise that you'll need to read up more in the documentation. In particular you'll need to query the Group object using a where expression that has a $relatedTo pointer for the user. To query in this manner, you will probably need a members property on the Group that is a relation to Users.
Something like this in your REST query might work (replace the objectId with the right User of course):
where={"$relatedTo":{"object":{"__type":"Pointer","className":"_User","objectId":"8TOXdXf3tz"},"key":"members"}}

Creating Mandatory User Filters with multiple element IDs

Mandatory User Filters
I am working on a tool to allow customers to apply Mandatory User Filters. When attributes are loaded like "Year" or "Age", each can have hundreds of elements with the subsequent ids. In the POST request to create a filter (documented here: https://developer.gooddata.com/article/lets-get-started-with-mandatory-user-filters), looks like this:
{
"userFilter": {
"content": {
"expression": "[/gdc/md/{project-id}/obj/{object-id}]=[/gdc/md/{project-id}/obj/{object-id}/elements?id={element-id}]"
},
"meta": {
"category": "userFilter",
"title": "My User Filter Name"
}
}
}
In the "expression" property, it notes how one ID could be set. What I want is to have multiple ids associated with the object-id set with the post. For example, if I user wanted to add a filter to all of the elements in "Year" (there are 150) in the demo project, it seems odd to make 150 post requests.
Is there a better way?
UPDATE
Tomas thank you for your help.
I am not having trouble assigning multiple userfilters to a user. I can easily apply a singular filter to a user with the method outlined in the documentation. However, this overwrites the userfilter field. What is the syntax for this?
Here is my demo POST data:
{ "userFilters":
{ "items": [
{ "user": "/gdc/account/profile/decd0b2e3077cf9c47f8cfbc32f6460e",
"userFilters":["/gdc/md/a1nc4jfa14wey1bnfs1vh9dljaf8ejuq/obj/808728","/gdc/md/a1nc4jfa14wey1bnfs1vh9dljaf8ejuq/obj/808729","/gdc/md/a1nc4jfa14wey1bnfs1vh9dljaf8ejuq/obj/808728"]
}
]
}
}
This receives a BAD REQUEST.
I'm not sure what you mean by "have multiple ids associated with the object-id" exactly, but I'll try to tell you all I know about it. :-)
If you indeed made multiple POST requests, created multiple userFilters and set them all for one user, the user wouldn't see anything at all. That's because the system combines separate userFilters using logical AND, and a Year cannot be 2013 and 2014 at the same time. So for the rest of my answer, I'll assume that you want OR instead.
There are several ways to do this. As you may have guessed by now, you can use AND/OR explicitly, using an expression like this:
[/…/obj/{object-id}]=[/…/obj/{object-id}/elements?id={element-id}] OR [/…/obj/{object-id}]=[/…/obj/{object-id}/elements?id={element-id}]
This can often be further simplified to:
[/…/obj/{object-id}] IN ( [/…/obj/{object-id}/elements?id={element-id}], [/…/obj/{object-id}/elements?id={element-id}], … )
If the attribute is a date (year, month, …) attribute, you could, in theory, also specify ranges using BETWEEN instead of listing all elements:
[/…/obj/{object-id}] BETWEEN [/…/obj/{object-id}/elements?id={element-id}] AND [/…/obj/{object-id}/elements?id={element-id}]
It seems, though, that this only works in metrics MAQL and is not allowed in the implementation of user filters. I have no idea why.
Also, for your own attribute like Age, you can't do that since user-defined numeric attributes aren't supported. You could, in theory, add a fact that holds the numeric value, and construct a BETWEEN filter based on that fact. It seems that this is not allowed in the implementation of user filters either. :-(
Hope this helps.

how to represent trees and their content in MySQL?

I need to be able to store something like this:
where the green is a template type, the gray is a field container type, and the white is field. (That being, a field has a label and some text, both mediumtext for simplicity; and a field container can store other field containers or text, and a template is a top-level field.)
Now, let's say I want to allow users to create any number of these structures, but it is unlikely to be more than say 10. And, of course, we need to be able to link data to it.
This is all to be able to store in a database an associative array that looks for the above like, in pseudo code:
my_template = {
page_info => { description => 'hello!!!' },
id => 0,
content => { background_url => '12121.jpg', text => ...
}
Having an easy way to add a field to all data using the template when the template changes (say, we add a keywords to page_info) would be a big plus.
I can't figure this out at all, thanks a lot!
There are several different ways to store heirarchical data structures (trees) in MySQL. You can for example choose:
Adjacency list
Nested sets
Path enumeration
Closure table
See Bill Karwin's presentation for more details on the pros and cons of each.