I am new to AVRO and please excuse me if it is a simple question.
I have a use case where I am using AVRO schema for record calls.
Let's say I have avro schema
{
"name": "abc",
"namepsace": "xyz",
"type": "record",
"fields": [
{"name": "CustId", "type":"string"},
{"name": "SessionId", "type":"string"},
]
}
Now if the input is like
{
"CustId" : "abc1234"
"sessionID" : "000-0000-00000"
}
I want to use some regex validations for these fields and I want take this input only if it comes in particular format shown as above. Is there any way to specify in avro schema to include regex expression?
Any other data serialization formats which supports something like this?
You should be able to use a custom logical type for this. You would then include the regular expressions directly in the schema.
For example, here's how you would implement one in JavaScript:
var avro = require('avsc'),
util = require('util');
/**
* Sample logical type that validates strings using a regular expression.
*
*/
function ValidatedString(attrs, opts) {
avro.types.LogicalType.call(this, attrs, opts);
this._pattern = new RegExp(attrs.pattern);
}
util.inherits(ValidatedString, avro.types.LogicalType);
ValidatedString.prototype._fromValue = function (val) {
if (!this._pattern.test(val)) {
throw new Error('invalid string: ' + val);
}
return val;
};
ValidatedString.prototype._toValue = ValidatedString.prototype._fromValue;
And how you would use it:
var type = avro.parse({
name: 'Example',
type: 'record',
fields: [
{
name: 'custId',
type: 'string' // Normal (free-form) string.
},
{
name: 'sessionId',
type: {
type: 'string',
logicalType: 'validated-string',
pattern: '^\\d{3}-\\d{4}-\\d{5}$' // Validation pattern.
}
},
]
}, {logicalTypes: {'validated-string': ValidatedString}});
type.isValid({custId: 'abc', sessionId: '123-1234-12345'}); // true
type.isValid({custId: 'abc', sessionId: 'foobar'}); // false
You can read more about implementing and using logical types here.
Edit: For the Java implementation, I believe you will want to look at the following classes:
LogicalType, the base you'll need to extend.
Conversion, to perform the conversion (or validation in your case) of the data.
LogicalTypes and Conversions, a few examples of existing implementations.
TestGenericLogicalTypes, relevant tests which could provide a helpful starting point.
Related
What I was hoping to do was store an array of objects using RedisJSON very simply and then query that array.
I have something similar to this:
const data = [
{
_id: '63e7d1d85ad7e2f69df8ed6e',
artist: {
genre: 'rock',
},
},
{
_id: '63e7d1d85ad7e2f69df8ed6f',
artist: {
genre: 'metal',
},
},
{
_id: '63e7d1d85ad7e2f69df8ed6g',
artist: {
genre: 'rock',
},
},
]
then I can easily store and retrieve this:
await redisClient.json.set(cacheKey, '$', data)
await redisClient.json.get(cacheKey)
works great. but now I want to also query this data, I've tried creating an index as below:
await redisClient.ft.create(
`idx:gigs`,
{
'$.[0].artist.genre': {
type: SchemaFieldTypes.TEXT,
AS: 'genre',
},
},
{
ON: 'JSON',
PREFIX: 'GIGS',
}
)
and when I try and search this index what I expect is it to return the 2 documents with the correct search filter, but instead it always returns the entire array:
const searchResult = await redisClient.ft.search(`idx:gigs`, '#genre:(rock)')
produces:
{
total: 1,
documents: [
{ id: 'cacheKey', value: [Array] }
]
}
I can't quite work out at which level I'm getting this wrong, but any help would be greatly appreciated.
Is it possible to store an array of objects and then search the nested objects for nested values with RedisJSON?
The Search capability in Redis stack treats each key containing a JSON document as a separate search index entry. I think what you are doing is perhaps storing your whole array of documents in a single Redis key, which means any matches will return the document at that key which contains all of your data.
I would suggest that you store each object in your data array as its own key in Redis. Make sure that these will be indexed by using the GIGS prefix in the key name, for example GIGS:63e7d1d85ad7e2f69df8ed6e and GIGS:63e7d1d85ad7e2f69df8ed6f.
You'd want to change your index definition to account for each document being an object too so it would look something like this:
await redisClient.ft.create(
`idx:gigs`,
{
'$.artist.genre': {
type: SchemaFieldTypes.TEXT,
AS: 'genre',
},
},
{
ON: 'JSON',
PREFIX: 'GIGS:',
}
)
Note I also updated your PREFIX to be GIGS: not GIGS - this isn't strictly necessary, but does stop your index from accidentally looking at other keys in Redis whose name begins GIGS<whatever other characters>.
i just know that the object Json can be an XMl file by the js2xml library,
so that's why I'm trying to convert the following json to XML,
How can I achieve this in NodeJS?
i can't find an answer or a documentation that can help me?
here is the model JSON
const UserSchema = new mongoose.Schema({
email: {
type: String,
required: [true, "Please provide email address"],
unique: true,
match: [
/^(([^<>()[\]\\.,;:\s#"]+(\.[^<>()[\]\\.,;:\s#"]+)*)|(".+"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]
{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
"Please provide a valid email",
],
},
password: {
type: String,
required: [true, "Please add a password"],
minlength: 6,
select: false,
},
const User = mongoose.model("User", UserSchema);
module.exports = User;
i used this exemple that didn't work for me
function groupChildren(obj) {
for(prop in obj) {
if (typeof obj[prop] === 'object') {
groupChildren(obj[prop]);
} else {
obj['$'] = obj['$'] || {};
obj['$'][prop] = obj[prop];
delete obj[prop];
}
}
return obj;
}
const xml2js = require('xml2js');
const obj = {
Level1: {
attribute: 'value',
Level2: {
attribute1: '05/29/2020',
attribute2: '10',
attribute3: 'Pizza'
}
}
};
const builder = new xml2js.Builder();
const xml = builder.buildObject(groupChildren(obj));
console.log(xml);
When converting JSON to XML, one has to ask: what XML do you want to convert it to? Does it have to be a specific XML format, or will any old XML do?
If any old XML will do, then you can usually find some library to do the job, such as js2xml or js2xmlparser. The problem with these libraries is that they usually offer very little control over how the XML is generated, especially for example if there are JSON objects with keys that are not valid XML names (which doesn't apply in your case).
If you want a specific XML format then I would recommend using XSLT 3.0, which is available on node.js in the form of Saxon-JS. [Disclaimer: my company's product]. If you are interested in pursuing this approach, then tell us what you want the output to look like, and we can help you create it.
There are many different packages for XML serialization.
Most of them enforce a specific XML and JSON mapping convention.
Others require you to build the XML document in code.
Finally, there are solutions that do this with decorators. Those give you freedom in defining the structure without having to build the document entirely in code.
As an example: the xml decorators package.
It means that you define the XML mapping using a class. Next, you define decorators on top of each field, to define how it should be mapped to XML.
import { XMLAttribute, xml } from 'xml-decorators';
const NS = 'ns';
export class User {
#XMLAttribute({namespace: NS})
private email:string;
#XMLAttribute({namespace: NS})
private password: string;
constructor(email: string, password: string) {
this.email = email;
this.password = password;
}
}
And finally, to actually serialize
const user = new User('foo#bar.com', 'secret');
const xml = xml.serialize(user);
Conceptually, this is certainly a robust solution, since it strongly resembles how java xml binding (JAXB) and C# xml serialization work.
I am using Indicative in my project to validate my controller, but, Indicative don't have a "Unique" rule in "Validation Rules", but the framework Adonis have a rule call "unique" that does exactly what i need.
My project is made in Adonis, but i prefer to use "Indicative" and not "Validator" in Adonis, because i think is more easy and beautiful write the code direct in the Controller
code: 'required|string|max:255',
description: 'required|string|max:255|unique:tabela',
authors: 'string|max:255',
status: 'boolean',
user_id: 'integer',
created_at: [
importValidate.validations.dateFormat(['YYYY-MM-DD HH:mm:ss'])
],
updated_at: [
importValidate.validations.dateFormat(['YYYY-MM-DD HH:mm:ss'])
]
}
In the example above, I need the "code" to be "Unique" and return an error message and a response status. How can I do this?
The unique method of Validator will automatically search in the database. I don't think it's possible to do it with Indicative
I propose this solution (in your controller):
const { validate } = use('Validator')
...
const rules = {
code: 'unique:<table_name>,<field_name>'
}
const messages = {
'code.unique': '...'
}
const validation = await validate({ code: ... }, rules, messages)
if (validation.fails()) {
...
}
To use this command it is necessary to use Validator. I don't think there's an equivalent with Indicative
I am having a problem searching for a key of a nested object.
I have search criteria object that may or may not have certain fields I'd like to search on.
The way I'm solving this is to use conditional statements to append to a "match criteria" object that gets passed to the aggregate $match operator. it works well until I need to match to something inside a nested object.
Here is a sample document structure
{
name: string,
dates: {
actived: Date,
suspended: Date
},
address : [{
street: string,
city: string,
state: string,
zip: string
}]
};
My criteria object is populated thru a UI and passed a JSON that looks similar to this:
{
"name": "",
"state": ""
}
And although I can explicitly use "dates.suspended" without issue -
when I try to append address.state to my search match criteria - I get an error.
module.exports.search = function( criteria, callback )
let matchCriteria = {
"name": criteria.name,
"dates.suspended": null
};
if ( criteria.state !== '' ) {
// *** PROBLEM HAPPENS HERE *** //
matchCriteria.address.state = criteria.state;
}
User.aggregate([
{ "$match": matchCriteria },
{ "$addFields": {...} },
{ "$project": {...} }
], callback );
}
I get the error:
TypeError: Cannot set property 'state' of undefined
I understand that I'm specifying 'address.state' when 'address' doesn't exist yet - but I am unclear what my syntax would be surely it woulnd't be matchCriteria['address.state'] or "matchCriteria.address.state"
Is there a better way to do conditional filtering?
For search in Nested Object, You have to use unwind
A query that help you :
//For testing declare criteria as const
let criteria = {name : 'name', 'state' : 'state'};
let addressMatch = {};
let matchCriteria = {
"name": criteria.name,
"dates.suspended": null
};
if ( criteria.state) {
addressMatch = { 'address.state' : criteria.state };
}
db.getCollection('user').aggregate([{
$match :matchCriteria,
},{$unwind:'$address'},
{$match : addressMatch}
])
Firstly check for address, and then access the property as shown:
if(matchCriteria['address']) {
matchCriteria['address']['state'] = criteria['state'];
}
else {
//otherwise
}
This should fix it:
matchCriteria['address.state'] = criteria.state;
I am new to node.js and newer to Sails.js framework.
I am currently trying to work with my database, I don't understand all the things with Sails.js but I manage to do what I want step by step. (I am used to some PHP MVC frameworks so it is not too difficult to understand the structure.)
Here I am trying to get a row from my database, using 2 JOIN clause. I managed to do this using SQL and the Model.query() function, but I would like to do this in a "cleaner" way.
So I have 3 tables in my database: meta, lang and meta_lang. It's quite simple and a picture being better than words, here are some screenshots.
meta
lang
meta_lang
What I want to do is to get the row in meta_table that match with 'default' meta and 'en' lang (for example).
Here are Meta and Lang models (I created them with sails generate model command and edited them with what I needed):
Meta
module.exports = {
attributes: {
code : { type: 'string' },
metaLangs:{
collection: 'MetaLang',
via: 'meta'
}
}
};
Lang
module.exports = {
attributes: {
code : { type: 'string' },
metaLangs:{
collection: 'MetaLang',
via: 'lang'
}
}
};
And here is my MetaLang model, with 3 functions I created to test several methods. The first function, findCurrent, works perfectly, but as you can see I had to write SQL. That is what I want to avoid if it is possible, I find it more clean (and I would like to use Sails.js tools as often as I can).
module.exports = {
tableName: 'meta_lang',
attributes: {
title : { type: 'string' },
description : { type: 'text' },
keywords : { type: 'string' },
meta:{
model:'Meta',
columnName: 'meta_id'
},
lang:{
model:'Lang',
columnName: 'lang_id'
}
},
findCurrent: function (metaCode, langCode) {
var query = 'SELECT ml.* FROM meta_lang ml INNER JOIN meta m ON m.id = ml.meta_id INNER JOIN lang l ON l.id = ml.lang_id WHERE m.code = ? AND l.code = ?';
MetaLang.query(query, [metaCode, langCode], function(err, metaLang) {
console.log('findCurrent');
if (err) return console.log(err);
console.log(metaLang);
// OK this works exactly as I want (I would have prefered a 'findOne' result, only 1 object instead of an array with 1 object in it, but I can do with it.)
});
},
findCurrentTest: function (metaCode, langCode) {
Meta.findByCode(metaCode).populate('metaLangs').exec(function(err, metaLang) {
console.log('findCurrentTest');
if (err) return console.log(err);
console.log(metaLang);
// I get what I expected (though not what I want): my meta + all metaLangs related to meta with code "default".
// What I want is to get ONE metaLang related to meta with code "default" AND lang with code "en".
});
},
findCurrentOthertest: function (metaCode, langCode) {
MetaLang.find().populate('meta', {where: {code:metaCode}}).populate('lang', {where: {code:langCode}}).exec(function(err, metaLang) {
console.log('findCurrentOthertest');
if (err) return console.log(err);
console.log(metaLang);
// Doesn't work as I wanted: it gets ALL the metaLang rows.
});
}
};
I also tried to first get my Meta by code, then my Lang by code, and MetaLang using Meta.id and Lang.id . But I would like to avoid 3 queries when I can have only one.
What I'm looking for would be something like MetaLang.find({meta.code:"default", lang.code:"en"}).
Hope you've got all needed details, just comment and ask for more if you don't.
Do you know what populate is for ? its for including the whole associated object when loading it from the database. Its practically the join you are trying to do, if all you need is row retrieval than quering the table without populate will make both functions you built work.
To me it looks like you are re-writing how Sails did the association. Id suggest giving the Associations docs another read in Sails documentation: Associations. As depending on your case you are just trying a one-to-many association with each table, you could avoid a middle table in my guess, but to decide better id need to understand your use-case.
When I saw the mySQL code it seemed to me you are still thinking in MySQL and PHP which takes time to convert from :) forcing the joins and middle tables yourself, redoing a lot of the stuff sails automated for you. I redone your example on 'disk' adapter and it worked perfectly. The whole point of WaterlineORM is to abstract the layer of going down to SQL unless absolutely necessary. Here is what I would do for your example, first without SQL just on a disk adapter id create the models :
// Lang.js
attributes: {
id :{ type: "Integer" , autoIncrement : true, primaryKey: true },
code :"string"
}
you see what i did redundantly here ? I did not really need the Id part as Sails does it for me. Just an example.
// Meta.js
attributes: {
code :"string"
}
better :) ?
// MetaLang.js
attributes:
{
title : "string",
desc : "string",
meta_id :
{
model : "meta",
},
lang_id :
{
model : "lang",
}
}
Now after simply creating the same values as your example i run sails console type :
MetaLang.find({meta_id : 1 ,lang_id:2}).exec(function(er,res){
console.log(res);
});
Output >>>
sails> [ { meta_id: 1,
lang_id: 2,
title: 'My blog',
id: 2 } ]
Now if you want to display what is meta with id 1 and what is lang with id 2, we use populate, but the referencing for join/search is just as simple as this.
sails> Meta_lang.find({meta_id : 1 ,lang_id:2}).populate('lang_id').populate('meta_id').exec(function(er,res){ console.log(res); });
undefined
sails> [ {
meta_id:
{ code: 'default',
id: 1 },
lang_id:
{ code: 'En',
id: 2 },
title: 'My blog',
id: 2 } ]
At this point, id switch adapters to MySQL and then create the MySQL tables with the same column names as above. Create the FK_constraints and voila.
Another strict policy you can add is to set up the 'via' and dominance on each model. you can read more about that in the Association documentation and it depends on the nature of association (many-to-many etc.)
To get the same result without knowing the Ids before-hand :
sails> Meta.findOne({code : "default"}).exec(function(err,needed_meta){
..... Lang.findOne({code : "En"}).exec(function(err_lang,needed_lang){
....... Meta_lang.find({meta_id : needed_meta.id , lang_id : needed_lang.id}).exec(function(err_all,result){
......... console.log(result);});
....... });
..... });
undefined
sails> [ { meta_id: 1,
lang_id: 2,
title: 'My blog',
id: 2 } ]
Have you tried:
findCurrentTest: function (metaCode, langCode) {
Meta.findByCode(metaCode).populate('metaLangs', {where: {code:langCode}}).exec(function(err, metaLang) {
console.log('findCurrentTest');
if (err) return console.log(err);
console.log(metaLang);
});
},