Where clause using included relation in `OR` statement - javascript

I'm moving a project from MySQL to Postgres using Sequelize, and there is one thing that has failed straight away. It looks to be due to the fact that 'User' isn't included anywhere.
Edit: the error message is 'missing FROM-clause entry for table users'.
The code for sequelize is:
List.model.findOne({
where: {
id: listId,
$or: [
{ open: true },
['Users.id = ?', [userId]], // <-- the line I think is breaking this.
],
},
include: [{
model: db.models.User
}],
});
I think this is due to User not being included in the FROM clause. But, I'm not sure how to make Sequelize do the right thing.
The query generated by Sequelize is:
SELECT "List"."id",
"List"."name",
"List"."image",
"List"."slug",
"List"."open",
"List"."password",
"List"."createdAt",
"List"."updatedAt",
"List"."OwnerId",
"Users"."id" AS "Users.id",
"Users"."firstName" AS "Users.firstName",
"Users"."lastName" AS "Users.lastName",
"Users"."email" AS "Users.email",
"Users"."password" AS "Users.password",
"Users"."image" AS "Users.image",
"Users"."facebookId" AS "Users.facebookId",
"Users"."googleId" AS "Users.googleId",
"Users"."createdAt" AS "Users.createdAt",
"Users"."updatedAt" AS "Users.updatedAt",
"Users.UserList"."createdAt" AS "Users.UserList.createdAt",
"Users.UserList"."updatedAt" AS "Users.UserList.updatedAt",
"Users.UserList"."ListId" AS "Users.UserList.ListId",
"Users.UserList"."UserId" AS "Users.UserList.UserId"
FROM "Lists" AS "List"
LEFT OUTER JOIN ("UserList" AS "Users.UserList"
INNER JOIN "Users" AS "Users" ON "Users"."id" = "Users.UserList"."UserId") ON "List"."id" = "Users.UserList"."ListId"
WHERE "List"."id" = 13
AND ("List"."open" = TRUE
OR (Users.id = 1234));

Try to wrap Users.id with double quotes like "Users"."id", because in PostgreSQL all unquoted identifiers (table names, fields) will be folded to lowercase and PostgreSQL is case-sensitive: https://www.postgresql.org/docs/current/static/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS
Quoting an identifier also makes it case-sensitive, whereas unquoted names are always folded to lower case. For example, the identifiers FOO, foo, and "foo" are considered the same by PostgreSQL, but "Foo" and "FOO" are different from these three and each other. (The folding of unquoted names to lower case in PostgreSQL is incompatible with the SQL standard, which says that unquoted names should be folded to upper case. Thus, foo should be equivalent to "FOO" not "foo" according to the standard. If you want to write portable applications you are advised to always quote a particular name or never quote it.)

Related

Mongodb : upsert and positional operator when query is false

I have a database like this :
Db.collection.object
{
"item1": [{
"quantity":"",
"price":""
}],
"item2": [{
"quantity":"",
"price":""
}],
}
I want to update "item2.quantity" so I'm using this (it's working):
db.object.updateOne({"item2":{$exists:true}},{$set:{"item2.$.quantity":"55"}})
But sometimes, I need to create "item2" so I want to do "upsert:true" so my "update function" will create the document.
db.object.updateOne({"item2":{$exists:true}},{$set:{"item2.$.quantity":"55"}}, {upsert:true})
But it does nothing because the query function is false ?
[thread1] WriteError: The positional operator did not find the match needed from the query.
To use the $, You must include the array field as part of the query document.
If the query matches the array using a negation operator, such as $ne, $not, or $nin, then you cannot use the positional operator to update values from this array.
Your $exists:true probably falls under negation
Official Document Here

GraphQL field with space

I'm trying to use a new GraphQL server on a very old legacy code, where the column names have spaces, e.g: "Poke ball"
I've been trying to run this query:
query{{userItems{Poke ball}}}
and got this:
extensions: {code: "GRAPHQL_VALIDATION_FAILED",…}
locations: [{line: 1, column: 12}]
message: "Cannot query field "Poke" on type "UserItems"."
I've tried to use quotes with no luck, any idea if this is supported / workaround?
Thanks.
The GraphQL specification requires that names of things (fields, types, arguments, etc.) only contain letters, numbers and underscores. A field name cannot contain a space because spaces and other whitespace are used to separate individual tokens. In other words, one or more spaces or line returns are used to indicate that, for example, one field's name has terminated and another has begun.
If your underlying data layer is returning data with keys that contain spaces, you need to define a field with an allowed name (like pokeball) and then write a resolver for that field. For example:
const UserItems = new GraphQLObjectType({
name: "UserItems",
fields: () => ({
pokeball: {
type: Pokeball,
resolve: (parent) => {
// The parent value will be whatever the parent field resolved to.
// We look for a property named "Poke ball" on that value and return it.
return parent["Poke ball"];
},
},
...
}),
});
or in the schema, do this
directive #fetch(from : String!) on FIELD_DEFINITION
type Product {
Pokeball : String #fetch(from:"Poke ball")
}

Mongodb Node.js driver embedded arrays query [duplicate]

I have a problem when querying mongoDB with nested objects notation:
db.messages.find( { headers : { From: "reservations#marriott.com" } } ).count()
0
db.messages.find( { 'headers.From': "reservations#marriott.com" } ).count()
5
I can't see what I am doing wrong. I am expecting nested object notation to return the same result as the dot notation query. Where am I wrong?
db.messages.find( { headers : { From: "reservations#marriott.com" } } )
This queries for documents where headers equals { From: ... }, i.e. contains no other fields.
db.messages.find( { 'headers.From': "reservations#marriott.com" } )
This only looks at the headers.From field, not affected by other fields contained in, or missing from, headers.
Dot-notation docs
Since there is a lot of confusion about queries MongoDB collection with sub-documents, I thought its worth to explain the above answers with examples:
First I have inserted only two objects in the collection namely: message as:
> db.messages.find().pretty()
{
"_id" : ObjectId("5cce8e417d2e7b3fe9c93c32"),
"headers" : {
"From" : "reservations#marriott.com"
}
}
{
"_id" : ObjectId("5cce8eb97d2e7b3fe9c93c33"),
"headers" : {
"From" : "reservations#marriott.com",
"To" : "kprasad.iitd#gmail.com"
}
}
>
So what is the result of query: db.messages.find({headers: {From: "reservations#marriott.com"} }).count()
It should be one because these queries for documents where headers equal to the object {From: "reservations#marriott.com"}, only i.e. contains no other fields or we should specify the entire sub-document as the value of a field.
So as per the answer from #Edmondo1984
Equality matches within sub-documents select documents if the subdocument matches exactly the specified sub-document, including the field order.
From the above statements, what is the below query result should be?
> db.messages.find({headers: {To: "kprasad.iitd#gmail.com", From: "reservations#marriott.com"} }).count()
0
And what if we will change the order of From and To i.e same as sub-documents of second documents?
> db.messages.find({headers: {From: "reservations#marriott.com", To: "kprasad.iitd#gmail.com"} }).count()
1
so, it matches exactly the specified sub-document, including the field order.
For using dot operator, I think it is very clear for every one. Let's see the result of below query:
> db.messages.find( { 'headers.From': "reservations#marriott.com" } ).count()
2
I hope these explanations with the above example will make someone more clarity on find query with sub-documents.
The two query mechanism work in different ways, as suggested in the docs at the section Subdocuments:
When the field holds an embedded document (i.e, subdocument), you can either specify the entire subdocument as the value of a field, or “reach into” the subdocument using dot notation, to specify values for individual fields in the subdocument:
Equality matches within subdocuments select documents if the subdocument matches exactly the specified subdocument, including the field order.
In the following example, the query matches all documents where the value of the field producer is a subdocument that contains only the field company with the value 'ABC123' and the field address with the value '123 Street', in the exact order:
db.inventory.find( {
producer: {
company: 'ABC123',
address: '123 Street'
}
});

Updating a Nested Array with MongoDB

I am trying to update a value in the nested array but can't get it to work.
My object is like this
{
"_id": {
"$oid": "1"
},
"array1": [
{
"_id": "12",
"array2": [
{
"_id": "123",
"answeredBy": [], // need to push "success"
},
{
"_id": "124",
"answeredBy": [],
}
],
}
]
}
I need to push a value to "answeredBy" array.
In the below example, I tried pushing "success" string to the "answeredBy" array of the "123 _id" object but it does not work.
callback = function(err,value){
if(err){
res.send(err);
}else{
res.send(value);
}
};
conditions = {
"_id": 1,
"array1._id": 12,
"array2._id": 123
};
updates = {
$push: {
"array2.$.answeredBy": "success"
}
};
options = {
upsert: true
};
Model.update(conditions, updates, options, callback);
I found this link, but its answer only says I should use object like structure instead of array's. This cannot be applied in my situation. I really need my object to be nested in arrays
It would be great if you can help me out here. I've been spending hours to figure this out.
Thank you in advance!
General Scope and Explanation
There are a few things wrong with what you are doing here. Firstly your query conditions. You are referring to several _id values where you should not need to, and at least one of which is not on the top level.
In order to get into a "nested" value and also presuming that _id value is unique and would not appear in any other document, you query form should be like this:
Model.update(
{ "array1.array2._id": "123" },
{ "$push": { "array1.0.array2.$.answeredBy": "success" } },
function(err,numAffected) {
// something with the result in here
}
);
Now that would actually work, but really it is only a fluke that it does as there are very good reasons why it should not work for you.
The important reading is in the official documentation for the positional $ operator under the subject of "Nested Arrays". What this says is:
The positional $ operator cannot be used for queries which traverse more than one array, such as queries that traverse arrays nested within other arrays, because the replacement for the $ placeholder is a single value
Specifically what that means is the element that will be matched and returned in the positional placeholder is the value of the index from the first matching array. This means in your case the matching index on the "top" level array.
So if you look at the query notation as shown, we have "hardcoded" the first ( or 0 index ) position in the top level array, and it just so happens that the matching element within "array2" is also the zero index entry.
To demonstrate this you can change the matching _id value to "124" and the result will $push an new entry onto the element with _id "123" as they are both in the zero index entry of "array1" and that is the value returned to the placeholder.
So that is the general problem with nesting arrays. You could remove one of the levels and you would still be able to $push to the correct element in your "top" array, but there would still be multiple levels.
Try to avoid nesting arrays as you will run into update problems as is shown.
The general case is to "flatten" the things you "think" are "levels" and actually make theses "attributes" on the final detail items. For example, the "flattened" form of the structure in the question should be something like:
{
"answers": [
{ "by": "success", "type2": "123", "type1": "12" }
]
}
Or even when accepting the inner array is $push only, and never updated:
{
"array": [
{ "type1": "12", "type2": "123", "answeredBy": ["success"] },
{ "type1": "12", "type2": "124", "answeredBy": [] }
]
}
Which both lend themselves to atomic updates within the scope of the positional $ operator
MongoDB 3.6 and Above
From MongoDB 3.6 there are new features available to work with nested arrays. This uses the positional filtered $[<identifier>] syntax in order to match the specific elements and apply different conditions through arrayFilters in the update statement:
Model.update(
{
"_id": 1,
"array1": {
"$elemMatch": {
"_id": "12","array2._id": "123"
}
}
},
{
"$push": { "array1.$[outer].array2.$[inner].answeredBy": "success" }
},
{
"arrayFilters": [{ "outer._id": "12" },{ "inner._id": "123" }]
}
)
The "arrayFilters" as passed to the options for .update() or even
.updateOne(), .updateMany(), .findOneAndUpdate() or .bulkWrite() method specifies the conditions to match on the identifier given in the update statement. Any elements that match the condition given will be updated.
Because the structure is "nested", we actually use "multiple filters" as is specified with an "array" of filter definitions as shown. The marked "identifier" is used in matching against the positional filtered $[<identifier>] syntax actually used in the update block of the statement. In this case inner and outer are the identifiers used for each condition as specified with the nested chain.
This new expansion makes the update of nested array content possible, but it does not really help with the practicality of "querying" such data, so the same caveats apply as explained earlier.
You typically really "mean" to express as "attributes", even if your brain initially thinks "nesting", it's just usually a reaction to how you believe the "previous relational parts" come together. In reality you really need more denormalization.
Also see How to Update Multiple Array Elements in mongodb, since these new update operators actually match and update "multiple array elements" rather than just the first, which has been the previous action of positional updates.
NOTE Somewhat ironically, since this is specified in the "options" argument for .update() and like methods, the syntax is generally compatible with all recent release driver versions.
However this is not true of the mongo shell, since the way the method is implemented there ( "ironically for backward compatibility" ) the arrayFilters argument is not recognized and removed by an internal method that parses the options in order to deliver "backward compatibility" with prior MongoDB server versions and a "legacy" .update() API call syntax.
So if you want to use the command in the mongo shell or other "shell based" products ( notably Robo 3T ) you need a latest version from either the development branch or production release as of 3.6 or greater.
See also positional all $[] which also updates "multiple array elements" but without applying to specified conditions and applies to all elements in the array where that is the desired action.
I know this is a very old question, but I just struggled with this problem myself, and found, what I believe to be, a better answer.
A way to solve this problem is to use Sub-Documents. This is done by nesting schemas within your schemas
MainSchema = new mongoose.Schema({
array1: [Array1Schema]
})
Array1Schema = new mongoose.Schema({
array2: [Array2Schema]
})
Array2Schema = new mongoose.Schema({
answeredBy": [...]
})
This way the object will look like the one you show, but now each array are filled with sub-documents. This makes it possible to dot your way into the sub-document you want. Instead of using a .update you then use a .find or .findOne to get the document you want to update.
Main.findOne((
{
_id: 1
}
)
.exec(
function(err, result){
result.array1.id(12).array2.id(123).answeredBy.push('success')
result.save(function(err){
console.log(result)
});
}
)
Haven't used the .push() function this way myself, so the syntax might not be right, but I have used both .set() and .remove(), and both works perfectly fine.

MongoDB dot (.) in key name

It seems mongo does not allow insertion of keys with a dot (.) or dollar sign ($) however when I imported a JSON file that contained a dot in it using the mongoimport tool it worked fine. The driver is complaining about trying to insert that element.
This is what the document looks like in the database:
{
"_id": {
"$oid": "..."
},
"make": "saab",
"models": {
"9.7x": [
2007,
2008,
2009,
2010
]
}
}
Am I doing this all wrong and should not be using hash maps like that with external data (i.e. the models) or can I escape the dot somehow? Maybe I am thinking too much Javascript-like.
MongoDB doesn't support keys with a dot in them so you're going to have to preprocess your JSON file to remove/replace them before importing it or you'll be setting yourself up for all sorts of problems.
There isn't a standard workaround to this issue, the best approach is too dependent upon the specifics of the situation. But I'd avoid any key encoder/decoder approach if possible as you'll continue to pay the inconvenience of that in perpetuity, where a JSON restructure would presumably be a one-time cost.
As mentioned in other answers MongoDB does not allow $ or . characters as map keys due to restrictions on field names. However, as mentioned in Dollar Sign Operator Escaping this restriction does not prevent you from inserting documents with such keys, it just prevents you from updating or querying them.
The problem of simply replacing . with [dot] or U+FF0E (as mentioned elsewhere on this page) is, what happens when the user legitimately wants to store the key [dot] or U+FF0E?
An approach that Fantom's afMorphia driver takes, is to use unicode escape sequences similar to that of Java, but ensuring the escape character is escaped first. In essence, the following string replacements are made (*):
\ --> \\
$ --> \u0024
. --> \u002e
A reverse replacement is made when map keys are subsequently read from MongoDB.
Or in Fantom code:
Str encodeKey(Str key) {
return key.replace("\\", "\\\\").replace("\$", "\\u0024").replace(".", "\\u002e")
}
Str decodeKey(Str key) {
return key.replace("\\u002e", ".").replace("\\u0024", "\$").replace("\\\\", "\\")
}
The only time a user needs to be aware of such conversions is when constructing queries for such keys.
Given it is common to store dotted.property.names in databases for configuration purposes I believe this approach is preferable to simply banning all such map keys.
(*) afMorphia actually performs full / proper unicode escaping rules as mentioned in Unicode escape syntax in Java but the described replacement sequence works just as well.
The latest stable version (v3.6.1) of the MongoDB does support dots (.) in the keys or field names now.
Field names can contain dots (.) and dollar ($) characters now
The Mongo docs suggest replacing illegal characters such as $ and . with their unicode equivalents.
In these situations, keys will need to substitute the reserved $ and . characters. Any character is sufficient, but consider using the Unicode full width equivalents: U+FF04 (i.e. “$”) and U+FF0E (i.e. “.”).
A solution I just implemented that I'm really happy with involves splitting the key name and value into two separate fields. This way, I can keep the characters exactly the same, and not worry about any of those parsing nightmares. The doc would look like:
{
...
keyName: "domain.com",
keyValue: "unregistered",
...
}
You can still query this easy enough, just by doing a find on the fields keyName and keyValue.
So instead of:
db.collection.find({"domain.com":"unregistered"})
which wouldn't actually work as expected, you would run:
db.collection.find({keyName:"domain.com", keyValue:"unregistered"})
and it will return the expected document.
You can try using a hash in the key instead of the value, and then store that value in the JSON value.
var crypto = require("crypto");
function md5(value) {
return crypto.createHash('md5').update( String(value) ).digest('hex');
}
var data = {
"_id": {
"$oid": "..."
},
"make": "saab",
"models": {}
}
var version = "9.7x";
data.models[ md5(version) ] = {
"version": version,
"years" : [
2007,
2008,
2009,
2010
]
}
You would then access the models using the hash later.
var version = "9.7x";
collection.find( { _id : ...}, function(e, data ) {
var models = data.models[ md5(version) ];
}
It is supported now
MongoDb 3.6 onwards supports both dots and dollar in field names.
See below JIRA: https://jira.mongodb.org/browse/JAVA-2810
Upgrading your Mongodb to 3.6+ sounds like the best way to go.
You'll need to escape the keys. Since it seems most people don't know how to properly escape strings, here's the steps:
choose an escape character (best to choose a character that's rarely used). Eg. '~'
To escape, first replace all instances of the escape character with some sequence prepended with your escape character (eg '~' -> '~t'), then replace whatever character or sequence you need to escape with some sequence prepended with your escape character. Eg. '.' -> '~p'
To unescape, first remove the escape sequence from all instance of your second escape sequence (eg '~p' -> '.'), then transform your escape character sequence to a single escape character(eg '~s' -> '~')
Also, remember that mongo also doesn't allow keys to start with '$', so you have to do something similar there
Here's some code that does it:
// returns an escaped mongo key
exports.escape = function(key) {
return key.replace(/~/g, '~s')
.replace(/\./g, '~p')
.replace(/^\$/g, '~d')
}
// returns an unescaped mongo key
exports.unescape = function(escapedKey) {
return escapedKey.replace(/^~d/g, '$')
.replace(/~p/g, '.')
.replace(/~s/g, '~')
}
From the MongoDB docs "the '.' character must not appear anywhere in the key name". It looks like you'll have to come up with an encoding scheme or do without.
A late answer, but if you use Spring and Mongo, Spring can manage the conversion for you with MappingMongoConverter. It's the solution by JohnnyHK but handled by Spring.
#Autowired
private MappingMongoConverter converter;
#PostConstruct
public void configureMongo() {
converter.setMapKeyDotReplacement("xxx");
}
If your stored Json is :
{ "axxxb" : "value" }
Through Spring (MongoClient) it will be read as :
{ "a.b" : "value" }
As another user mentioned, encoding/decoding this can become problematic in the future, so it's probably just easier to replace all keys that have a dot. Here's a recursive function I made to replace keys with '.' occurrences:
def mongo_jsonify(dictionary):
new_dict = {}
if type(dictionary) is dict:
for k, v in dictionary.items():
new_k = k.replace('.', '-')
if type(v) is dict:
new_dict[new_k] = mongo_jsonify(v)
elif type(v) is list:
new_dict[new_k] = [mongo_jsonify(i) for i in v]
else:
new_dict[new_k] = dictionary[k]
return new_dict
else:
return dictionary
if __name__ == '__main__':
with open('path_to_json', "r") as input_file:
d = json.load(input_file)
d = mongo_jsonify(d)
pprint(d)
You can modify this code to replace '$' too, as that is another character that mongo won't allow in a key.
I use the following escaping in JavaScript for each object key:
key.replace(/\\/g, '\\\\').replace(/^\$/, '\\$').replace(/\./g, '\\_')
What I like about it is that it replaces only $ at the beginning, and it does not use unicode characters which can be tricky to use in the console. _ is to me much more readable than an unicode character. It also does not replace one set of special characters ($, .) with another (unicode). But properly escapes with traditional \.
Not perfect, but will work in most situations: replace the prohibited characters by something else. Since it's in keys, these new chars should be fairly rare.
/** This will replace \ with ⍀, ^$ with '₴' and dots with ⋅ to make the object compatible for mongoDB insert.
Caveats:
1. If you have any of ⍀, ₴ or ⋅ in your original documents, they will be converted to \$.upon decoding.
2. Recursive structures are always an issue. A cheap way to prevent a stack overflow is by limiting the number of levels. The default max level is 10.
*/
encodeMongoObj = function(o, level = 10) {
var build = {}, key, newKey, value
//if (typeof level === "undefined") level = 20 // default level if not provided
for (key in o) {
value = o[key]
if (typeof value === "object") value = (level > 0) ? encodeMongoObj(value, level - 1) : null // If this is an object, recurse if we can
newKey = key.replace(/\\/g, '⍀').replace(/^\$/, '₴').replace(/\./g, '⋅') // replace special chars prohibited in mongo keys
build[newKey] = value
}
return build
}
/** This will decode an object encoded with the above function. We assume the structure is not recursive since it should come from Mongodb */
decodeMongoObj = function(o) {
var build = {}, key, newKey, value
for (key in o) {
value = o[key]
if (typeof value === "object") value = decodeMongoObj(value) // If this is an object, recurse
newKey = key.replace(/⍀/g, '\\').replace(/^₴/, '$').replace(/⋅/g, '.') // replace special chars prohibited in mongo keys
build[newKey] = value
}
return build
}
Here is a test:
var nastyObj = {
"sub.obj" : {"$dollar\\backslash": "$\\.end$"}
}
nastyObj["$you.must.be.kidding"] = nastyObj // make it recursive
var encoded = encodeMongoObj(nastyObj, 1)
console.log(encoded)
console.log( decodeMongoObj( encoded) )
and the results - note that the values are not modified:
{
sub⋅obj: {
₴dollar⍀backslash: "$\\.end$"
},
₴you⋅must⋅be⋅kidding: {
sub⋅obj: null,
₴you⋅must⋅be⋅kidding: null
}
}
[12:02:47.691] {
"sub.obj": {
$dollar\\backslash: "$\\.end$"
},
"$you.must.be.kidding": {
"sub.obj": {},
"$you.must.be.kidding": {}
}
}
There is some ugly way to query it not recommended to use it in application rather than for debug purposes (works only on embedded objects):
db.getCollection('mycollection').aggregate([
{$match: {mymapfield: {$type: "object" }}}, //filter objects with right field type
{$project: {mymapfield: { $objectToArray: "$mymapfield" }}}, //"unwind" map to array of {k: key, v: value} objects
{$match: {mymapfield: {k: "my.key.with.dot", v: "myvalue"}}} //query
])
For PHP I substitute the HTML value for the period. That's ".".
It stores in MongoDB like this:
"validations" : {
"4e25adbb1b0a55400e030000" : {
"associate" : "true"
},
"4e25adb11b0a55400e010000" : {
"associate" : "true"
}
}
and the PHP code...
$entry = array('associate' => $associate);
$data = array( '$set' => array( 'validations.' . str_replace(".", `"."`, $validation) => $entry ));
$newstatus = $collection->update($key, $data, $options);
Lodash pairs will allow you to change
{ 'connect.sid': 's:hyeIzKRdD9aucCc5NceYw5zhHN5vpFOp.0OUaA6' }
into
[ [ 'connect.sid',
's:hyeIzKRdD9aucCc5NceYw5zhHN5vpFOp.0OUaA6' ] ]
using
var newObj = _.pairs(oldObj);
You can store it as it is and convert to pretty after
I wrote this example on Livescript. You can use livescript.net website to eval it
test =
field:
field1: 1
field2: 2
field3: 5
nested:
more: 1
moresdafasdf: 23423
field3: 3
get-plain = (json, parent)->
| typeof! json is \Object => json |> obj-to-pairs |> map -> get-plain it.1, [parent,it.0].filter(-> it?).join(\.)
| _ => key: parent, value: json
test |> get-plain |> flatten |> map (-> [it.key, it.value]) |> pairs-to-obj
It will produce
{"field.field1":1,
"field.field2":2,
"field.field3":5,
"field.nested.more":1,
"field.nested.moresdafasdf":23423,
"field3":3}
Give you my tip: You can using JSON.stringify to save Object/ Array contains the key name has dots, then parse string to Object with JSON.parse to process when get data from database
Another workaround:
Restructure your schema like:
key : {
"keyName": "a.b"
"value": [Array]
}
Latest MongoDB does support keys with a dot, but java MongoDB-driver is not supporting. So to make it work in Java, I pulled code from github repo of java-mongo-driver and made changes accordingly in their isValid Key function, created new jar out of it, using it now.
Replace the dot(.) or dollar($) with other characters that will never used in the real document. And restore the dot(.) or dollar($) when retrieving the document. The strategy won't influence the data that user read.
You can select the character from all characters.
The strange this is, using mongojs, I can create a document with a dot if I set the _id myself, however I cannot create a document when the _id is generated:
Does work:
db.testcollection.save({"_id": "testdocument", "dot.ted.": "value"}, (err, res) => {
console.log(err, res);
});
Does not work:
db.testcollection.save({"dot.ted": "value"}, (err, res) => {
console.log(err, res);
});
I first thought dat updating a document with a dot key also worked, but its identifying the dot as a subkey!
Seeing how mongojs handles the dot (subkey), I'm going to make sure my keys don't contain a dot.
Like what #JohnnyHK has mentioned, do remove punctuations or '.' from your keys because it will create much larger problems when your data starts to accumulate into a larger dataset. This will cause problems especially when you call aggregate operators like $merge which requires accessing and comparing keys which will throw an error. I have learnt it the hard way please don't repeat for those who are starting out.
In our case the properties with the period is never queried by users directly. However, they can be created by users.
So we serialize our entire model first and string replace all instances of the specific fields. Our period fields can show up in many location and it is not predictable what the structure of the data is.
var dataJson = serialize(dataObj);
foreach(pf in periodFields)
{
var encodedPF = pf.replace(".", "ENCODE_DOT");
dataJson.replace(pf, encodedPF);
}
Then later after our data is flattened we replace instances of the encodedPF so we can write the decoded version in our files
Nobody will ever need a field named ENCODE_DOT so it will not be an issue in our case.
The result is the following
color.one will be in the database as colorENCODE_DOTone
When we write our files we replace ENCODE_DOT with .
/home/user/anaconda3/lib/python3.6/site-packages/pymongo/collection.py
Found it in error messages. If you use anaconda (find the correspondent file if not), simply change the value from check_keys = True to False in the file stated above. That'll work!

Categories

Resources