How to search JCR nodes with a version label - jcr

Following code segment is used to add a label to a node using ModeShape. What query string I could use to query this node with the QueryManager? It seems that the nodes from versionHistory side cannot be queried with the QueryManager.
VersionHistory history = session.getWorkspace().getVersionManager()
.getVersionHistory(node.getPath());
history.addVersionLabel(version.getName(), "label", true);
I cannot find any nodes with the following query relating to version history. Is there anything missing from this query?
Query query = queryManager
.createQuery("select * from [nt:base]", Query.JCR_SQL2);
Thanks!

All of the version information is stored on the nt:versionHistory nodes under the /jcr:system/jcr:versionStorage area of the workspace (shared amongst all workspaces in a repository). The structure of this area looks something like this:
+ jcr:system
+ jcr:versionStorage {jcr:primaryType = mode:versionStorage}
+ b4 {jcr:primaryType = mode:versionHistoryFolder}
+ 6d {jcr:primaryType = mode:versionHistoryFolder}
+ de {jcr:primaryType = mode:versionHistoryFolder}
+ 298905f76361779339fa3ccacc4f47664255 {jcr:primaryType = nt:versionHistory}
+ jcr:versionLabels {jcr:primaryType = nt:versionLabels}
+ jcr:rootVersion {jcr:primaryType = nt:version}
- jcr:uuid = ...
- jcr:created = ...
+ jcr:frozenNode {jcr:primaryType = nt:frozenNode}
- jcr:frozenUuid
- jcr:frozenPrimaryType
- jcr:frozenMixinTypes
The jcr:versionLabels node contains a property for each label, where the name of the property is the label string and the value of the property is a REFERENCE to the version to which that label applies.
In order to find the labeled version of a versionable node, you'll actually have to query the contents of this "/jcr:system/jcr:versionStorage" area, using the appropriate types and join constraints.
For example, to find the version with a particular label "foo", you'd do something like this:
SELECT versioned.* FROM [nt:frozenNode] AS versioned JOIN [nt:version] AS version ON ISCHILDNODE(versioned,version) JOIN [nt:versionLabels] AS label ON label.myLabel = version.[jcr:uuid]
This search all of the nt:versionLabels nodes looking for a property named 'myLabel' (which is the name of your label), and then finds the nt:version node to which that points, and the nt:frozenNode that is the copy of the versionable node for that version.
As you can see, the version history representations as prescribed by the JCR specification are quite difficult to query.
Alternatively, you could do this in two steps:
use a query to find the jcr:uuid of the version(s) that has a particular label
for each of those identifiers, find the UUID of the versionable node and use the VersionManager to look up the version history for that node and find the labeled version.

Related

How can use executeQueryWithParameters with SQLBuilderSelectExpression to join an x++/sql statement in Microsoft Dynamics?

In Dynamics 365 for Finance and Operations, they describe a method of creating SQL statements "as objects, as opposed to text", but this is somewhat of a lie. They use the objects to create the text which then populates str sqlStatement = selectExpr.getExpression(null);
This sqlStatement would then feed the obsolete statement.executeQuery(sqlStatement);.
I can make the warning go away by using executeQueryWithParameters() with an empty map (SqlParams::create()) as the second parameter, but this seems to be "cheating".
Is there a way I can/should refactor the following to populate the map correctly?
SQLBuilderSelectExpression selectExpression = SQLBuilderSelectExpression::construct();
selectExpression.parmUseJoin(true);
SQLBuilderTableEntry vendTable = selectExpression.addTableId(tableNum(VendTable));
SQLBuilderTableEntry dirPartyTable = vendTable.addJoinTableId(tableNum(DirPartyTable));
SQLBuilderFieldEntry accountNum = vendTable.addFieldId(fieldNum(VendTable, AccountNum));
SQLBuilderFieldEntry name = dirPartyTable.addFieldId(fieldNum(DirPartyTable, Name));
SQLBuilderFieldEntry dataAreaId = vendTable.addFieldId(fieldNum(VendTable, dataAreaId));
SQLBuilderFieldEntry blocked = vendTable.addFieldId(fieldNum(VendTable, Blocked));
vendTable.addRange(dataAreaId, curext());
vendTable.addRange(blocked, CustVendorBlocked::No);
selectExpression.addSelectFieldEntry(SQLBuilderSelectFieldEntry::newExpression(accountNum, 'AccountNum'));
selectExpression.addSelectFieldEntry(SQLBuilderSelectFieldEntry::newExpression(name, 'Name'));
str sqlStatement = selectExpression.getExpression(null);
// FIXME:
ResultSet resultSet = statement.executeQueryWithParameters(sqlStatement, SqlParams::create());
Below is how you would write your code as a standard X++ query. However, I must note that what you're doing may not be the best approach.
DirPartyTable is a special table in AX as it supports inheritance, so you should make sure you fully understand the framework. See:
https://learn.microsoft.com/en-us/dynamicsax-2012/appuser-itpro/implementing-the-global-address-book-framework-white-paper
https://learn.microsoft.com/en-us/dynamics365/fin-ops-core/fin-ops/organization-administration/overview-global-address-book
Code:
VendTable vendTable;
DirPartyTable dirPartyTable;
while select AccountNum from vendTable
where vendTable.Blocked == CustVendorBlocked::No
// DataAreaId along with Partition, are automatically included in the query context depending
// on the company context you're executing the code from
// && vendTable.dataAreaId == curext()
join Name from dirPartyTable
where dirPartyTable.RecId == vendTable.Party
{
info(strFmt("Account: %1; Name: %2", vendTable.AccountNum, dirPartyTable.Name));
}
Regarding an AOT query, look in the AOT at \Queries\VendTableListPage and expand the data sources and learn from it.
Regardless of what OP is trying to do with the query, the answer to the question of "how do I correctly replace executeQuery with executeQueryWithParameters" can be found in the following article.
https://learn.microsoft.com/en-us/dynamics365/fin-ops-core/dev-itpro/dev-ref/query-with-parameters
The new *WithParameters APIs were introduced as a way to mitigate sql injection attacks which may occur when building up sql strings manually with un-sanitized sql parameters as input.
Snippet of the code example from above doc shows how to correctly populate the map to match the sql statement:
str sql = #"
UPDATE Wages
SET Wages.Wage = Wages.Wage * #percent
WHERE Wages.Level = #Level";
Map paramMap = SqlParams::create();
paramMap.add('percent', 1.1); // 10 percent increase
paramMap.add('Level', 'Manager'); // Management increase
int cnt = statement.executeUpdateWithParameters(sql, paramMap);

Spellcheck not working in search in AEM 6.1

Spell check is not working in AEM 6.1 even after creating the suggested indexes as specified in --
https://docs.adobe.com/docs/en/aem/6-1/deploy/platform/queries-and-indexing.html and
https://jackrabbit.apache.org/oak/docs/query/lucene.html
As per the docs spellcheck has been implemented in OAK since versions 1.1.17 and 1.0.13. My OAK version is 1.22 so it should work.The AEM version is 6.1.0.20150507
Created an index like -
/oak:index/lucene-spellcheck
- jcr:primaryType = "oak:QueryIndexDefinition"
- compatVersion = 2
- type = "lucene"
- async = "async"
+ indexRules
- jcr:primaryType = "nt:unstructured"
+ nt:base
+ properties
- jcr:primaryType = "nt:unstructured"
+ jcr:title
- propertyIndex = true
- analyzed = true
- useInSpellcheck = true
I get zero results when I run this query using CRX-DE's query tool as well as using query manager from a jsp.
SELECT [rep:spellcheck()] FROM nt:base WHERE [jcr:path] = '/content/abc' AND SPELLCHECK('tetspage')
('testpage' is a page; misspelled it as 'tetspage')
Running this query as mentioned in the docs
SELECT [rep:spellcheck()] FROM nt:base WHERE [jcr:path] = '/' AND SPELLCHECK('jackrabit')
returns a single node.
Am I doing anything wrong; is this index working in anyone's AEM 6.1 ?
This query works for me
SELECT [rep:spellcheck()] FROM [nt:base] WHERE SPELLCHECK('tetspage') AND ISDESCENDANTNODE('/content/abc')
I had a similar problem with a "rep:suggest()" query, instead of a "rep:spellcheck()", but maybe the solution works for you.
When I tried the query in the CRX DE directly, the result was a single node, and I couldn't see anything on that node.
However, executing the query from my code I found out that the results of the query were stored as "rows". To process that "rows" I used this code, hope it helps you:
String sql = "SELECT [rep:suggest()] FROM cq:PageContent WHERE ISDESCENDANTNODE('" + path + "/') AND SUGGEST('" + text + "')";
Query q = qm.createQuery(sql, Query.SQL);
List<String> results = Lists.newArrayList(); // <--- Actual query results
RowIterator it = q.execute().getRows();
while (it.hasNext()) {
Row row = it.nextRow();
results.add(row.getValue("rep:suggest()").getString());
}

Pyodbc and Access with query parameter that contains a period

I recently found a bug with some Access SQL queries that I can't seem to track down. I have a fairly straightforward SQL query that I use to retrieve data from an access database that's "managed" in an older application (ie the data is already in the database and I have no real control over what's in there).
import pyodbc
MDB = '******.MDB'
DRV = '{Microsoft Access Driver (*.mdb)}'
PWD = ''
con = pyodbc.connect('DRIVER={};DBQ={};PWD={}'.format(DRV, MDB, PWD))
sql = ('SELECT Estim.PartNo, Estim.Descrip, Estim.CustCode, Estim.User_Text1, Estim.Revision, ' +
'Estim.Comments, Routing.PartNo AS RPartNo, Routing.StepNo, Routing.WorkCntr, Routing.VendCode, ' +
'Routing.Descrip AS StepDescrip, Routing.SetupTime, Routing.CycleTime, ' +
'Routing.WorkOrVend, ' +
'Materials.PartNo as MatPartNo, Materials.SubPartNo, Materials.Qty, ' +
'Materials.Unit, Materials.TotalQty, Materials.ItemNo, Materials.Vendor ' +
'FROM (( Estim ' +
'INNER JOIN Routing ON Estim.PartNo = Routing.PartNo ) ' +
'INNER JOIN Materials ON Estim.PartNo = Materials.PartNo )')
if 'PartNo' in kwargs:
key = kwargs['PartNo']
sql = sql + 'WHERE Estim.PartNo=?'
cursor = con.cursor().execute(sql, key)
# use this for debuging only
num = 0
for row in cursor.fetchall():
num += 1
return num
This works fine for all PartNo except when PartNo contains a decimal point. Curiously, when PartNo contains a decimal point AND a hyphen, I get the appropriate record(s).
kwargs['PartNo'] = "100.100-2" # returns 1 record
kwargs['PartNo'] = "200.100" # returns 0 records
Both PartNos exist when viewed in the other application, so I know there should be records returned for both queries.
My first thought was to ensure kwargs['PartNo'] is a string key = str(kwargs['PartNo']) with no change.
I also tried to places quotes around the 'PartNo' value with no success. key = '\'' + kwargs['PartNo'] + '\''
Finally, I tried to escape the . with no success (I realize this would break most queries, but I'm just trying to track down the issue with a single period) key = str(kwargs['partNo']).replace('.', '"."')
I know using query parameters should handle all the escaping for me, but at this point, I'm just trying to figure out what's going on. Any thoughts on this?
So the issue isn't with the query parameters - everything works as it should. The problem is with the SQL statement. I incorrectly assumed - and never checked - that there was a record in the Materials table that matched PartNo.
INNER JOIN Materials ON Estim.PartNo = Materials.PartNo
will only return a record if PartNo is found in both tables, which in this particular case it is not.
Changing it to
LEFT OUTER JOIN Materials ON Estim.PartNo = Materials.PartNo
produces the expected results. See this for info on JOINS. https://msdn.microsoft.com/en-us/library/bb243855(v=office.12).aspx
As for print (repr(key)) - flask handles the kwarg type upstream properly
api.add_resource(PartAPI, '/api/v1.0/part/<string:PartNo>'
so when I ran this in the browser, I got the "full length" strings. When run in the cmd line using python -c ....... I was not handling the argument type properly as Gord pointed out, so it was truncating the trailing zeros. I didn't think the flask portion was relevant, so I never added that in the original question.

Modeshape querying mixinTypes

I'm using Modeshape and modeshape-connector-jdbc-metadata. I want to get all nodes representing tables in the storage. That nodes have [mj:catalog] mixin type.
I'm querying storage using next code:
public List getDatabases() throws RepositoryException {
// Obtain the query manager for the session ...
QueryManager queryManager = dbSession.getWorkspace().getQueryManager();
// Create a query object ...
Query query = queryManager.createQuery("SELECT * FROM [mj:table]"
, Query.JCR_SQL2);
// Execute the query and get the results ...
QueryResult result = query.execute();
// Iterate over the nodes in the results ...
NodeIterator nodeIter = result.getNodes();
List stringResult = new ArrayList();
while (nodeIter.hasNext()) {
stringResult.add(nodeIter.nextNode().getName());
}
return stringResult;
}
But it always returns empty list.
I also tried to query using next queries:
SELECT unst.*, tbl.* FROM [nt:unstructured] AS unst
JOIN [mj:table] AS tbl ON ISSAMENODE(unst,tbl)
SELECT * FROM [nt:unstructured] WHERE [jcr:mixinTypes] = [mj:table]
But result remains the same.
What I'm doing wrong?
Thank you for any help.
There is a known issue that the database metadata nodes are not indexed automatically. A simple workaround is to cast the JCR Session's getWorkspace() instance to org.modeshape.jcr.api.Workspace (the public API for ModeShape's workspace) and call the reindex(String path) method and passing in the path to the database catalog node (or an ancestor if desired).

(drupal)a difficulty code to understand,get the same article's title under the same term

if ($node->taxonomy) {
$query = 'SELECT DISTINCT(t.nid), n.nid, n.title FROM {node} n INNER JOIN {term_node} t ON n.nid = t.nid WHERE n.nid != %d AND (';
$args = array($node->nid);
$tids = array();
foreach ($node->taxonomy as $term) {
$tids[] = 't.tid = %d';
$args[] = $term->tid;
}
$query .= implode(' OR ', $tids) . ')';
$result = db_query_range($query, $args, 0, 10);
while ($o = db_fetch_object($result)) {
echo l($o->title, 'node/' . $o->nid);
}
}
the code is from a drupal guru. . used to get the article's title under the same term in node.tpl.php, i have researched it two days, although know some part of it. the principle of the code i still don't know. expect someone can explain more details about it for me .many thanks.
Short version:
It gets the array of tags of the node, retrieves the first 10 nodes that use at least one of these tags and outputs a link for each of these 10 results.
Detailed version:
First of all, the variable "$node" is an object that contains the data about a specific node (e.g. a Page or Story node).
For example, "$node->title" would be the title of that node.
"$node->taxonomy" tests is that node is tagged (because if it has no tags, it cannot retrieve the other nodes using the same tag(s).
When there is one or several tags associated with that node/page/story, $node->taxonomy is an array .
Now about the SQL query:
"node" is the database table that stores the base fields (non-CCK) of every node.
"term_node" is the database table that contains the combination of tag (which is called a "taxonomy term") and node.
In both tables, "nid" is the "unique Node ID" (which is an internal autoincremented number). Because this column is in both tables, this is how the tables are joined together.
In "term_node", "tid" is the "unique Term ID" (which is also an internal autoincremented number).
The "node" table is aliased "n", therefore "n.nid" means "the Node ID stored in table node".
The "term_node" table is aliased "t", therefore "t.tid" means "the Term ID stored in table term_node".
The "foreach" loop goes thru the array of tags to extract the TermID of each tag used by the node in order to add it in the SQL query, and implode converts to a string.
The loop stores a piece of SQL query for each tag in variable $tids and stores the actual value in variable $args because Drupal database calls are safer when the arguments are passed separately from the SQL query: "%d" means "integer number".
"db_query_range" is a function that selects multiple rows in the database: here, "0 10" means "retrieve the first 10 results".
"db_fetch_object" in the "while" loop retrieves each result and stores it in the variable "$o", which is an object.
Therefore "$o->title" contains the value of the column "title" retrieved by the SQL query.
The function "l" is the drupal functin that creates the code for an HTML link: the first argument is the name of the link, the second argument is the drupal path: in Drupal, any node can be accessed by default using "www.yoursite.com/node/NodeID",
which is why it gives the path "node/123" (where 123 is the "Node ID").
This function is useful because it transparently handles custom paths, so if your node has a custom path to access it using "www.yoursite.com/my-great-page" instead, it will create a link to that page instead of "www.yoursite.com/node/123" automatically.
I wouldn't exactly call the guy who wrote this a guru, you could do this a lot prettier. Anyways what he does it create a query that looks like this:
SELECT DISTINCT(t.nid), n.nid, n.title FROM {node} n
INNER JOIN {term_node} t ON n.nid = t.nid
WHERE n.nid != %d
AND (t.tid = %d OR t.tid = %d OR ... t.tid = %d);
The end result is that he selects all the node ids and titles (only once) that share at least one term with the selected node, but isn't the node itself.