Page Menu
Home
Sealhub
Search
Configure Global Search
Log In
Files
F995408
query-step.ts
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
7 KB
Referenced Files
None
Subscribers
None
query-step.ts
View Options
import
object_hash
from
"object-hash"
;
import
transformObject
from
"../utils/transform-object.js"
;
import
negate_stage
from
"./negate-stage.js"
;
import
type
QueryStage
from
"./query-stage.js"
;
import
type
{
MatchBody
}
from
"./query-stage.js"
;
export
default
abstract
class
QueryStep
{
body
:
any
;
hash
()
:
string
{
return
QueryStep
.
hashBody
(
this
.
body
);
}
static
fromStage
(
stage
:
QueryStage
,
unwind
=
true
,
rehash
=
false
)
:
QueryStep
[]
{
if
(
stage
.
$lookup
)
{
const
clonedStageBody
=
{
...
stage
.
$lookup
};
clonedStageBody
.
unwind
=
unwind
;
return
[
Lookup
.
fromBody
(
clonedStageBody
,
rehash
)];
}
else
if
(
stage
.
$match
)
{
return
Object
.
keys
(
stage
.
$match
).
map
(
(
field
)
=>
new
Match
({
[
field
]
:
stage
?
.
$match
?
.[
field
]
})
);
}
else
if
(
stage
.
$group
)
{
return
[
new
Group
(
stage
.
$group
)];
}
else
if
(
stage
.
$unwind
)
{
return
[
new
Unwind
(
stage
.
$unwind
)];
}
throw
new
Error
(
"Unsupported stage: "
+
JSON
.
stringify
(
stage
));
}
static
hashBody
(
body
:
any
)
{
return
object_hash
(
body
,
{
algorithm
:
"md5"
,
excludeKeys
:
(
key
)
=>
key
===
"as"
,
});
}
abstract
getUsedFields
()
:
string
[];
abstract
getCost
()
:
number
;
abstract
negate
()
:
QueryStep
;
abstract
prefix
(
prefix
:
string
)
:
QueryStep
;
abstract
toPipeline
()
:
QueryStage
[];
abstract
renameField
(
old_name
:
string
,
new_name
:
string
)
:
void
;
}
export
class
Match
extends
QueryStep
{
body
:
MatchBody
;
constructor
(
body
:
MatchBody
)
{
super
();
this
.
body
=
body
;
if
(
!
body
)
{
throw
new
Error
(
"no body!"
);
}
}
toPipeline
()
:
[
QueryStage
]
{
return
[{
$match
:
this
.
body
}];
}
pushStage
(
pipeline
:
QueryStage
[])
:
QueryStage
[]
{
pipeline
.
push
({
$match
:
this
.
body
});
return
pipeline
;
}
getUsedFields
()
:
string
[]
{
return
getAllKeys
(
this
.
body
)
.
map
((
path
)
=>
path
.
split
(
"."
))
.
reduce
((
acc
,
fields
)
=>
acc
.
concat
(
fields
.
filter
((
field
)
=>
!
field
.
startsWith
(
"$"
)))
);
}
getCost
()
:
number
{
return
this
.
body
.
$or
?
2
:
0
;
}
negate
()
:
Match
{
return
new
Match
(
negate_stage
(
this
.
body
as
QueryStage
));
}
prefix
(
prefix
:
string
)
:
Match
{
const
prop_regex
=
/^[a-z0-9_]/
;
const
ret
:
MatchBody
=
{};
for
(
const
[
prop
,
value
]
of
Object
.
entries
(
this
.
body
))
{
const
new_prop
=
prop_regex
.
test
(
prop
)
&&
!
Array
.
isArray
(
value
)
?
prefix
+
"."
+
prop
:
prop
;
if
(
prop
==
"$or"
||
prop
==
"$and"
||
prop
==
"$nor"
)
{
const
new_values
=
(
value
as
MatchBody
[]).
map
(
(
match_body
)
=>
new
Match
(
match_body
).
prefix
(
prefix
).
body
);
ret
[
new_prop
]
=
new_values
;
}
else
if
(
prop
===
"$in"
)
{
ret
[
new_prop
]
=
(
value
as
string
[]).
map
((
v
)
=>
v
.
replace
(
/^\$/
,
"$"
+
prefix
+
"."
)
);
}
else
if
(
value
instanceof
Object
)
{
ret
[
new_prop
]
=
new
Match
(
value
as
MatchBody
).
prefix
(
prefix
).
body
;
}
else
{
if
(
typeof
value
===
"string"
)
{
ret
[
new_prop
]
=
value
.
startsWith
(
"$"
)
?
value
.
replace
(
"$"
,
"$"
+
prefix
+
"."
)
:
value
;
}
else
{
ret
[
new_prop
]
=
value
;
}
}
}
return
new
Match
(
ret
);
}
renameField
(
old_name
:
string
,
new_name
:
string
)
:
void
{
this
.
body
=
transformObject
(
this
.
body
,
(
prop
)
=>
{
if
(
prop
===
old_name
)
{
return
new_name
;
}
if
(
prop
.
split
(
"."
)[
0
]
===
old_name
)
{
return
[
new_name
,
...
prop
.
split
(
"."
).
slice
(
1
)].
join
(
"."
);
}
return
prop
;
},
(
prop
,
value
)
=>
value
);
}
}
function
getAllKeys
(
obj
:
any
)
:
string
[]
{
return
Object
.
keys
(
obj
).
reduce
((
acc
,
key
)
=>
{
if
(
obj
[
key
]
instanceof
Object
)
{
acc
.
push
(...
getAllKeys
(
obj
[
key
]));
}
if
(
!
Array
.
isArray
(
obj
))
{
acc
.
push
(
key
);
}
return
acc
;
},
[]
as
string
[]);
}
export
type
SimpleLookupBodyInput
=
{
from
:
string
;
localField
:
string
;
foreignField
:
string
;
unwind
?:
boolean
;
as
?:
string
;
};
export
type
SimpleLookupBody
=
SimpleLookupBodyInput
&
{
as
:
string
};
export
type
ComplexLookupBodyInput
=
{
from
:
string
;
let
:
Record
<
string
,
string
>
;
pipeline
:
QueryStage
[];
unwind
?:
boolean
;
as
?:
string
;
};
export
type
ComplexLookupBody
=
ComplexLookupBodyInput
&
{
as
:
string
};
export
type
LookupBody
=
ComplexLookupBody
|
SimpleLookupBody
;
export
type
LookupBodyInput
=
ComplexLookupBodyInput
|
SimpleLookupBodyInput
;
export
abstract
class
Lookup
extends
QueryStep
{
abstract
getUsedFields
()
:
string
[];
body
:
LookupBody
;
unwind
:
boolean
;
constructor
(
body
:
SimpleLookupBodyInput
|
ComplexLookupBodyInput
,
rehash
=
false
)
{
super
();
let
hash
:
string
=
body
.
as
||
Lookup
.
hashBody
(
body
);
if
(
!
body
.
as
||
rehash
)
{
hash
=
Lookup
.
hashBody
(
body
);
}
this
.
body
=
{
...
body
,
as
:
hash
,
};
this
.
unwind
=
body
.
unwind
||
false
;
}
static
hashBody
(
body
:
LookupBodyInput
)
:
string
{
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const
{
as
,
...
rest
}
=
body
;
return
QueryStep
.
hashBody
(
rest
);
}
getCost
()
:
number
{
return
8
;
}
negate
()
:
QueryStep
{
return
this
;
}
hash
()
:
string
{
if
(
!
this
.
body
.
as
)
{
throw
new
Error
(
"Cannot hash a lookup step without an `as` property"
);
}
return
this
.
body
.
as
;
}
static
isComplexBody
(
body
:
ComplexLookupBodyInput
|
SimpleLookupBodyInput
)
:
body
is
ComplexLookupBodyInput
{
return
Object
.
prototype
.
hasOwnProperty
.
call
(
body
,
"let"
)
as
boolean
;
}
static
fromBody
(
body
:
ComplexLookupBodyInput
|
SimpleLookupBodyInput
,
rehash
=
false
)
:
Lookup
{
if
(
Lookup
.
isComplexBody
(
body
))
{
return
new
ComplexLookup
(
body
,
rehash
);
}
else
{
return
new
SimpleLookup
(
body
,
rehash
);
}
}
toPipeline
()
:
QueryStage
[]
{
const
ret
=
{
$lookup
:
{
...
this
.
body
}
};
delete
ret
.
$lookup
.
unwind
;
return
[
ret
,
...(
this
.
body
.
unwind
?
[{
$unwind
:
`$
${
this
.
body
.
as
}
`
}]
:
[]),
];
}
renameField
(
old_name
:
string
,
new_name
:
string
)
{
Function
.
prototype
();
//noop
}
}
export
class
SimpleLookup
extends
Lookup
{
unwind
:
boolean
;
body
:
SimpleLookupBody
;
used_fields
:
string
[];
getUsedFields
()
:
string
[]
{
return
this
.
body
.
localField
.
split
(
"."
);
}
prefix
(
prefix
:
string
)
{
return
new
SimpleLookup
({
from
:
this
.
body
.
from
,
localField
:
`
${
prefix
}
.
${
this
.
body
.
localField
}
`
,
foreignField
:
this
.
body
.
foreignField
,
unwind
:
this
.
unwind
,
as
:
`
${
prefix
}
.
${
this
.
body
.
as
}
`
,
});
}
}
export
class
ComplexLookup
extends
Lookup
{
body
:
ComplexLookupBody
;
getUsedFields
()
:
string
[]
{
return
Object
.
values
(
this
.
body
.
let
).
map
((
entry
)
=>
entry
.
replace
(
/\$/g
,
""
)
);
}
prefix
(
prefix
:
string
)
:
Lookup
{
const
ret
=
new
ComplexLookup
({
from
:
this
.
body
.
from
,
let
:
Object
.
fromEntries
(
Object
.
entries
(
this
.
body
.
let
).
map
(([
key
,
value
])
=>
[
key
,
value
.
replace
(
"$"
,
"$"
+
prefix
+
"."
),
])
),
pipeline
:
this
.
body
.
pipeline
,
// .map((stage) =>
// QueryStep.fromStage(stage).map((step) =>
// step.prefix(prefix)
// )
// )
// .reduce((acc, cur) => acc.concat(cur))
// .map((step) => step.toPipeline())
// .reduce((acc, cur) => acc.concat(cur)),
as
:
prefix
+
"."
+
this
.
body
.
as
,
});
return
ret
;
}
}
abstract
class
SimpleQueryStep
<
T
>
{
abstract
getStageName
:
()
=>
string
;
constructor
(
public
body
:
T
)
{}
prefix
()
:
SimpleQueryStep
<
T
>
{
return
this
;
}
getCost
()
{
return
2
;
}
toPipeline
()
:
QueryStage
[]
{
return
[{
[
this
.
getStageName
()]
:
this
.
body
}];
}
hash
()
{
return
object_hash
(
this
.
body
);
}
getUsedFields
()
{
return
[];
}
negate
()
{
return
this
;
}
// eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars
renameField
(
_
:
string
,
__
:
string
)
:
void
{}
}
export
class
Group
extends
SimpleQueryStep
<
{
_id
:
any
;
[
key
:
string
]
:
any
}
>
{
getStageName
=
()
=>
"$group"
;
}
export
class
Unwind
extends
SimpleQueryStep
<
string
>
{
getStageName
=
()
=>
"$unwind"
;
renameField
(
old_name
:
string
,
new_name
:
string
)
:
void
{
if
((
this
.
body
=
"$"
+
old_name
))
{
this
.
body
=
"$"
+
new_name
;
}
}
}
File Metadata
Details
Attached
Mime Type
text/x-java
Expires
Mon, Dec 23, 03:36 (17 h, 9 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
556804
Default Alt Text
query-step.ts (7 KB)
Attached To
Mode
rS Sealious
Attached
Detach File
Event Timeline
Log In to Comment