Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
jio
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Bryan Kaperick
jio
Commits
1f034e93
Commit
1f034e93
authored
Aug 03, 2018
by
Bryan Kaperick
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Changed the name of historystorage.
parent
a1d4c506
Changes
4
Show whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
3745 additions
and
6708 deletions
+3745
-6708
src/jio.storage/historystorage.js
src/jio.storage/historystorage.js
+0
-619
src/jio.storage/revisionstorage.js
src/jio.storage/revisionstorage.js
+566
-1011
test/jio.storage/historystorage.tests.js
test/jio.storage/historystorage.tests.js
+0
-3267
test/jio.storage/revisionstorage.tests.js
test/jio.storage/revisionstorage.tests.js
+3179
-1811
No files found.
src/jio.storage/historystorage.js
deleted
100644 → 0
View file @
a1d4c506
/*jslint nomen: true*/
/*global RSVP, SimpleQuery, ComplexQuery*/
(
function
(
jIO
,
RSVP
,
SimpleQuery
,
ComplexQuery
)
{
"
use strict
"
;
// Used to distinguish between operations done within the same millisecond
function
generateUniqueTimestamp
(
time
)
{
// XXX: replace this with UUIDStorage function call to S4() when it becomes
// publicly accessible
var
uuid
=
(
'
0000
'
+
Math
.
floor
(
Math
.
random
()
*
0x10000
)
.
toString
(
16
)).
slice
(
-
4
),
//timestamp = Date.now().toString();
timestamp
=
time
.
toString
();
return
timestamp
+
"
-
"
+
uuid
;
}
function
isTimestamp
(
id
)
{
//A timestamp is of the form
//"[13 digit number]-[4 numbers/lowercase letters]"
var
re
=
/^
[
0-9
]{13}
-
[
a-z0-9
]{4}
$/
;
return
re
.
test
(
id
);
}
function
removeOldRevs
(
substorage
,
results
,
keepDoc
)
{
var
ind
,
promises
=
[],
seen
=
{},
docum
,
log
,
start_ind
,
new_promises
,
doc_id
,
checkIsId
,
removeDoc
;
for
(
ind
=
0
;
ind
<
results
.
data
.
rows
.
length
;
ind
+=
1
)
{
docum
=
results
.
data
.
rows
[
ind
];
// Count the number of revisions of each document, and delete older
// ones.
if
(
!
seen
.
hasOwnProperty
(
docum
.
value
.
doc_id
))
{
seen
[
docum
.
value
.
doc_id
]
=
{
count
:
0
};
}
log
=
seen
[
docum
.
value
.
doc_id
];
log
.
count
+=
1
;
//log.id = docum.id;
// Record the index of the most recent edit that is before the cutoff
if
(
!
log
.
hasOwnProperty
(
"
s
"
)
&&
!
keepDoc
({
doc
:
docum
,
log
:
log
}))
{
log
.
s
=
ind
;
}
// Record the index of the most recent put or remove
if
((
!
log
.
hasOwnProperty
(
"
pr
"
))
&&
(
docum
.
value
.
op
===
"
put
"
||
docum
.
value
.
op
===
"
remove
"
))
{
log
.
pr
=
ind
;
log
.
final
=
ind
;
}
if
((
docum
.
op
===
"
putAttachment
"
||
docum
.
op
===
"
removeAttachment
"
)
&&
log
.
hasOwnProperty
(
docum
.
name
)
&&
!
log
[
docum
.
name
].
hasOwnProperty
(
"
prA
"
))
{
log
[
docum
.
name
].
prA
=
ind
;
log
.
final
=
ind
;
}
}
checkIsId
=
function
(
d
)
{
return
d
.
value
.
doc_id
===
doc_id
;
};
removeDoc
=
function
(
d
)
{
return
substorage
.
remove
(
d
.
id
);
};
for
(
doc_id
in
seen
)
{
if
(
seen
.
hasOwnProperty
(
doc_id
))
{
log
=
seen
[
doc_id
];
start_ind
=
Math
.
max
(
log
.
s
,
log
.
final
+
1
);
new_promises
=
results
.
data
.
rows
.
slice
(
start_ind
)
.
filter
(
checkIsId
)
.
map
(
removeDoc
);
promises
=
promises
.
concat
(
new_promises
);
}
}
return
RSVP
.
all
(
promises
);
}
function
throwCantFindError
(
id
)
{
throw
new
jIO
.
util
.
jIOError
(
"
HistoryStorage: cannot find object '
"
+
id
+
"
'
"
,
404
);
}
function
throwRemovedError
(
id
)
{
throw
new
jIO
.
util
.
jIOError
(
"
HistoryStorage: cannot find object '
"
+
id
+
"
' (removed)
"
,
404
);
}
/**
* The jIO HistoryStorage extension
*
* @class HistoryStorage
* @constructor
*/
function
HistoryStorage
(
spec
)
{
this
.
_sub_storage
=
jIO
.
createJIO
(
spec
.
sub_storage
);
if
(
spec
.
hasOwnProperty
(
"
include_revisions
"
))
{
this
.
_include_revisions
=
spec
.
include_revisions
;
}
else
{
this
.
_include_revisions
=
false
;
}
var
substorage
=
this
.
_sub_storage
;
this
.
packOldRevisions
=
function
(
save_info
)
{
/**
save_info has this form:
{
keep_latest_num: 10,
keep_active_revs: timestamp
}
keep_latest_num = x: keep at most the x latest copies of each unique doc
keep_active_revs = x: throw away all outdated revisions from before x
**/
var
options
=
{
sort_on
:
[[
"
timestamp
"
,
"
descending
"
]],
select_list
:
[
"
doc
"
,
"
doc_id
"
,
"
op
"
]
},
keep_fixed_num
=
save_info
.
hasOwnProperty
(
"
keep_latest_num
"
);
return
substorage
.
allDocs
(
options
)
.
push
(
function
(
results
)
{
if
(
keep_fixed_num
)
{
return
removeOldRevs
(
substorage
,
results
,
function
(
data
)
{
return
data
.
log
.
count
<=
save_info
.
keep_latest_num
;
});
}
return
removeOldRevs
(
substorage
,
results
,
function
(
data
)
{
return
data
.
doc
.
id
>
save_info
.
keep_active_revs
;
});
});
};
}
HistoryStorage
.
prototype
.
get
=
function
(
id_in
)
{
// Query to get the last edit made to this document
var
substorage
=
this
.
_sub_storage
,
doc_id_query
,
metadata_query
,
options
;
if
(
this
.
_include_revisions
)
{
doc_id_query
=
new
SimpleQuery
({
operator
:
"
<=
"
,
key
:
"
timestamp
"
,
value
:
id_in
});
}
else
{
doc_id_query
=
new
SimpleQuery
({
key
:
"
doc_id
"
,
value
:
id_in
});
}
// Include id_in as value in query object for safety
metadata_query
=
new
ComplexQuery
({
operator
:
"
AND
"
,
query_list
:
[
doc_id_query
,
new
ComplexQuery
({
operator
:
"
OR
"
,
query_list
:
[
new
SimpleQuery
({
key
:
"
op
"
,
value
:
"
remove
"
}),
new
SimpleQuery
({
key
:
"
op
"
,
value
:
"
put
"
})
]
})
]
});
options
=
{
query
:
metadata_query
,
select_list
:
[
"
op
"
],
limit
:
[
0
,
1
],
sort_on
:
[[
"
timestamp
"
,
"
descending
"
]]
};
return
substorage
.
allDocs
(
options
)
.
push
(
function
(
results
)
{
if
(
results
.
data
.
total_rows
>
0
)
{
if
(
results
.
data
.
rows
[
0
].
value
.
op
===
"
put
"
)
{
return
substorage
.
get
(
results
.
data
.
rows
[
0
].
id
)
.
push
(
function
(
result
)
{
return
result
.
doc
;
});
}
throwRemovedError
(
id_in
);
}
throwCantFindError
(
id_in
);
});
};
HistoryStorage
.
prototype
.
put
=
function
(
id
,
data
)
{
var
substorage
=
this
.
_sub_storage
,
timestamp
=
generateUniqueTimestamp
(
Date
.
now
()),
metadata
=
{
// XXX: remove this attribute once query can sort_on id
timestamp
:
timestamp
,
doc_id
:
id
,
doc
:
data
,
op
:
"
put
"
};
if
(
this
.
_include_revisions
&&
isTimestamp
(
id
))
{
return
substorage
.
get
(
id
)
.
push
(
function
(
metadata
)
{
metadata
.
timestamp
=
timestamp
;
metadata
.
doc
=
data
;
return
substorage
.
put
(
timestamp
,
metadata
);
},
function
(
error
)
{
if
(
error
.
status_code
===
404
&&
error
instanceof
jIO
.
util
.
jIOError
)
{
return
substorage
.
put
(
timestamp
,
metadata
);
}
throw
error
;
});
}
return
this
.
_sub_storage
.
put
(
timestamp
,
metadata
);
};
HistoryStorage
.
prototype
.
remove
=
function
(
id
)
{
var
timestamp
=
generateUniqueTimestamp
(
Date
.
now
()
-
1
),
metadata
=
{
// XXX: remove this attribute once query can sort_on id
timestamp
:
timestamp
,
doc_id
:
id
,
op
:
"
remove
"
};
return
this
.
_sub_storage
.
put
(
timestamp
,
metadata
);
};
HistoryStorage
.
prototype
.
allAttachments
=
function
(
id
)
{
var
substorage
=
this
.
_sub_storage
,
query_obj
,
query_removed_check
,
options
,
query_doc_id
,
options_remcheck
,
include_revs
=
this
.
_include_revisions
,
have_seen_id
=
false
;
// id is a timestamp, and allAttachments will return attachment versions
// up-to-and-including those made at time id
if
(
include_revs
)
{
query_doc_id
=
new
SimpleQuery
({
operator
:
"
<=
"
,
key
:
"
timestamp
"
,
value
:
id
});
}
else
{
query_doc_id
=
new
SimpleQuery
({
key
:
"
doc_id
"
,
value
:
id
});
have_seen_id
=
true
;
}
query_removed_check
=
new
ComplexQuery
({
operator
:
"
AND
"
,
query_list
:
[
query_doc_id
,
new
ComplexQuery
({
operator
:
"
OR
"
,
query_list
:
[
new
SimpleQuery
({
key
:
"
op
"
,
value
:
"
put
"
}),
new
SimpleQuery
({
key
:
"
op
"
,
value
:
"
remove
"
})
]
})
]
});
query_obj
=
new
ComplexQuery
({
operator
:
"
AND
"
,
query_list
:
[
query_doc_id
,
new
ComplexQuery
({
operator
:
"
OR
"
,
query_list
:
[
new
SimpleQuery
({
key
:
"
op
"
,
value
:
"
putAttachment
"
}),
new
SimpleQuery
({
key
:
"
op
"
,
value
:
"
removeAttachment
"
})
]
})
]
});
options_remcheck
=
{
query
:
query_removed_check
,
select_list
:
[
"
op
"
,
"
timestamp
"
],
limit
:
[
0
,
1
],
sort_on
:
[[
"
timestamp
"
,
"
descending
"
]]
};
options
=
{
query
:
query_obj
,
sort_on
:
[[
"
timestamp
"
,
"
descending
"
]],
select_list
:
[
"
op
"
,
"
name
"
]
};
return
this
.
_sub_storage
.
allDocs
(
options_remcheck
)
// Check the document exists and is not removed
.
push
(
function
(
results
)
{
if
(
results
.
data
.
total_rows
>
0
)
{
if
(
results
.
data
.
rows
[
0
].
id
===
id
)
{
have_seen_id
=
true
;
}
if
(
results
.
data
.
rows
[
0
].
value
.
op
===
"
remove
"
)
{
throwRemovedError
(
id
);
}
}
else
{
throwCantFindError
(
id
);
}
})
.
push
(
function
()
{
return
substorage
.
allDocs
(
options
);
})
.
push
(
function
(
results
)
{
var
seen
=
{},
attachments
=
[],
attachment_promises
=
[],
ind
,
entry
;
// If input mapped to a real timestamp, then the first query result must
// have the inputted id. Otherwise, unexpected results could arise
// by inputting nonsensical strings as id when include_revisions = true
if
(
include_revs
&&
results
.
data
.
total_rows
>
0
&&
results
.
data
.
rows
[
0
].
id
!==
id
&&
!
have_seen_id
)
{
throwCantFindError
(
id
);
}
// Only return attachments if:
// (it is the most recent revision) AND (it is a putAttachment)
attachments
=
results
.
data
.
rows
.
filter
(
function
(
docum
)
{
if
(
!
seen
.
hasOwnProperty
(
docum
.
value
.
name
))
{
var
output
=
(
docum
.
value
.
op
===
"
putAttachment
"
);
seen
[
docum
.
value
.
name
]
=
{};
return
output
;
}
});
// Assembles object of attachment_name: attachment_object
for
(
ind
=
0
;
ind
<
attachments
.
length
;
ind
+=
1
)
{
entry
=
attachments
[
ind
];
attachment_promises
[
entry
.
value
.
name
]
=
substorage
.
getAttachment
(
entry
.
id
,
entry
.
value
.
name
);
}
return
RSVP
.
hash
(
attachment_promises
);
});
};
HistoryStorage
.
prototype
.
putAttachment
=
function
(
id
,
name
,
blob
)
{
var
timestamp
=
generateUniqueTimestamp
(
Date
.
now
()),
metadata
=
{
// XXX: remove this attribute once query can sort_on id
timestamp
:
timestamp
,
doc_id
:
id
,
name
:
name
,
op
:
"
putAttachment
"
},
substorage
=
this
.
_sub_storage
;
if
(
this
.
_include_revisions
&&
isTimestamp
(
id
))
{
return
substorage
.
get
(
id
)
.
push
(
function
(
metadata
)
{
metadata
.
timestamp
=
timestamp
;
metadata
.
name
=
name
;
},
function
(
error
)
{
if
(
!
(
error
.
status_code
===
404
&&
error
instanceof
jIO
.
util
.
jIOError
))
{
throw
error
;
}
});
}
return
this
.
_sub_storage
.
put
(
timestamp
,
metadata
)
.
push
(
function
()
{
return
substorage
.
putAttachment
(
timestamp
,
name
,
blob
);
});
};
HistoryStorage
.
prototype
.
getAttachment
=
function
(
id
,
name
)
{
// In this case, id is a timestamp, so return attachment version at that
// time
if
(
this
.
_include_revisions
)
{
return
this
.
_sub_storage
.
getAttachment
(
id
,
name
)
.
push
(
undefined
,
function
(
error
)
{
if
(
error
.
status_code
===
404
&&
error
instanceof
jIO
.
util
.
jIOError
)
{
throwCantFindError
(
id
);
}
throw
error
;
});
}
// Query to get the last edit made to this document
var
substorage
=
this
.
_sub_storage
,
// "doc_id: id AND
// (op: remove OR ((op: putAttachment OR op: removeAttachment) AND
// name: name))"
metadata_query
=
new
ComplexQuery
({
operator
:
"
AND
"
,
query_list
:
[
new
SimpleQuery
({
key
:
"
doc_id
"
,
value
:
id
}),
new
ComplexQuery
({
operator
:
"
OR
"
,
query_list
:
[
new
SimpleQuery
({
key
:
"
op
"
,
value
:
"
remove
"
}),
new
ComplexQuery
({
operator
:
"
AND
"
,
query_list
:
[
new
ComplexQuery
({
operator
:
"
OR
"
,
query_list
:
[
new
SimpleQuery
({
key
:
"
op
"
,
value
:
"
putAttachment
"
}),
new
SimpleQuery
({
key
:
"
op
"
,
value
:
"
removeAttachment
"
})
]
}),
new
SimpleQuery
({
key
:
"
name
"
,
value
:
name
})
]
})
]
})
]
}),
options
=
{
query
:
metadata_query
,
sort_on
:
[[
"
timestamp
"
,
"
descending
"
]],
limit
:
[
0
,
1
],
select_list
:
[
"
op
"
,
"
name
"
]
};
return
substorage
.
allDocs
(
options
)
.
push
(
function
(
results
)
{
if
(
results
.
data
.
total_rows
>
0
)
{
// XXX: issue if attachments are put on a removed document
if
(
results
.
data
.
rows
[
0
].
value
.
op
===
"
remove
"
||
results
.
data
.
rows
[
0
].
value
.
op
===
"
removeAttachment
"
)
{
throwRemovedError
(
id
);
}
return
substorage
.
getAttachment
(
results
.
data
.
rows
[
0
].
id
,
name
);
}
throwCantFindError
(
id
);
});
};
HistoryStorage
.
prototype
.
removeAttachment
=
function
(
id
,
name
)
{
var
timestamp
=
generateUniqueTimestamp
(
Date
.
now
()),
metadata
=
{
// XXX: remove this attribute once query can sort_on id
timestamp
:
timestamp
,
doc_id
:
id
,
name
:
name
,
op
:
"
removeAttachment
"
};
return
this
.
_sub_storage
.
put
(
timestamp
,
metadata
);
};
HistoryStorage
.
prototype
.
repair
=
function
()
{
return
this
.
_sub_storage
.
repair
.
apply
(
this
.
_sub_storage
,
arguments
);
};
HistoryStorage
.
prototype
.
hasCapacity
=
function
(
name
)
{
return
name
===
'
list
'
||
name
===
'
include
'
||
name
===
'
query
'
||
name
===
'
select
'
;
};
HistoryStorage
.
prototype
.
buildQuery
=
function
(
options
)
{
// Set default values
if
(
options
===
undefined
)
{
options
=
{};
}
if
(
options
.
query
===
undefined
)
{
options
.
query
=
""
;
}
if
(
options
.
sort_on
===
undefined
)
{
options
.
sort_on
=
[];
}
if
(
options
.
select_list
===
undefined
)
{
options
.
select_list
=
[];
}
options
.
query
=
jIO
.
QueryFactory
.
create
(
options
.
query
);
var
meta_options
=
{
query
:
""
,
sort_on
:
[[
"
timestamp
"
,
"
descending
"
]],
select_list
:
[
"
doc
"
,
"
op
"
,
"
doc_id
"
,
"
timestamp
"
]
},
include_revs
=
this
.
_include_revisions
;
if
(
include_revs
)
{
// && options.query.key === "doc_id") {
meta_options
.
query
=
options
.
query
;
}
return
this
.
_sub_storage
.
allDocs
(
meta_options
)
.
push
(
function
(
results
)
{
results
=
results
.
data
.
rows
;
var
seen
=
{},
docs_to_query
,
i
;
if
(
include_revs
)
{
// We only query on versions mapping to puts or putAttachments
results
=
results
.
map
(
function
(
docum
,
ind
)
{
var
data_key
;
if
(
docum
.
value
.
op
===
"
put
"
)
{
return
docum
;
}
if
(
docum
.
value
.
op
===
"
remove
"
)
{
docum
.
value
.
doc
=
{};
return
docum
;
}
if
(
docum
.
value
.
op
===
"
putAttachment
"
||
docum
.
value
.
op
===
"
removeAttachment
"
)
{
// putAttachment document does not contain doc metadata, so we
// add it from the most recent non-removed put on same id
docum
.
value
.
doc
=
{};
for
(
i
=
ind
+
1
;
i
<
results
.
length
;
i
+=
1
)
{
if
(
results
[
i
].
value
.
doc_id
===
docum
.
value
.
doc_id
)
{
if
(
results
[
i
].
value
.
op
===
"
put
"
)
{
for
(
data_key
in
results
[
i
].
value
.
doc
)
{
if
(
results
[
i
].
value
.
doc
.
hasOwnProperty
(
data_key
))
{
docum
.
value
.
doc
[
data_key
]
=
results
[
i
].
value
.
doc
[
data_key
];
}
}
return
docum
;
}
// If most recent metadata edit before the attachment edit
// was a remove, then leave doc empty
if
(
results
[
i
].
value
.
op
===
"
remove
"
)
{
return
docum
;
}
}
}
}
return
false
;
});
}
else
{
// Only query on latest revisions of non-removed documents/attachment
// edits
results
=
results
.
map
(
function
(
docum
,
ind
)
{
var
data_key
;
if
(
docum
.
value
.
op
===
"
put
"
)
{
// Mark as read and include in query
if
(
!
seen
.
hasOwnProperty
(
docum
.
value
.
doc_id
))
{
seen
[
docum
.
value
.
doc_id
]
=
{};
return
docum
;
}
}
else
if
(
docum
.
value
.
op
===
"
remove
"
||
docum
.
value
.
op
===
"
removeAttachment
"
)
{
// Mark as read but do not include in query
seen
[
docum
.
value
.
doc_id
]
=
{};
}
else
if
(
docum
.
value
.
op
===
"
putAttachment
"
)
{
// If latest edit, mark as read, add document metadata from most
// recent put, and add to query
if
(
!
seen
.
hasOwnProperty
(
docum
.
value
.
doc_id
))
{
seen
[
docum
.
value
.
doc_id
]
=
{};
docum
.
value
.
doc
=
{};
for
(
i
=
ind
+
1
;
i
<
results
.
length
;
i
+=
1
)
{
if
(
results
[
i
].
value
.
doc_id
===
docum
.
value
.
doc_id
)
{
if
(
results
[
i
].
value
.
op
===
"
put
"
)
{
for
(
data_key
in
results
[
i
].
value
.
doc
)
{
if
(
results
[
i
].
value
.
doc
.
hasOwnProperty
(
data_key
))
{
docum
.
value
.
doc
[
data_key
]
=
results
[
i
].
value
.
doc
[
data_key
];
}
}
return
docum
;
}
if
(
results
[
i
].
value
.
op
===
"
remove
"
)
{
// If most recent edit on document was a remove before
// this attachment, then don't include attachment in query
return
false
;
}
docum
.
value
.
doc
=
{};
}
}
}
}
return
false
;
});
}
docs_to_query
=
results
// Filter out all docs flagged as false in previous map call
.
filter
(
function
(
docum
)
{
return
docum
;
})
// Put into correct format to be passed back to query storage
.
map
(
function
(
docum
)
{
if
(
include_revs
)
{
docum
.
id
=
docum
.
value
.
timestamp
;
//docum.doc = docum.value.doc;
}
else
{
docum
.
id
=
docum
.
value
.
doc_id
;
//docum.doc = docum.value.doc;
}
delete
docum
.
value
.
doc_id
;
delete
docum
.
value
.
timestamp
;
delete
docum
.
value
.
op
;
docum
.
value
=
docum
.
value
.
doc
;
//docum.value = {};
return
docum
;
});
return
docs_to_query
;
});
};
jIO
.
addStorage
(
'
history
'
,
HistoryStorage
);
}(
jIO
,
RSVP
,
SimpleQuery
,
ComplexQuery
));
\ No newline at end of file
src/jio.storage/revisionstorage.js
View file @
1f034e93
/*jslint indent: 2, maxlen: 80, nomen: true */
/*jslint nomen: true*/
/*global jIO, hex_sha256, define */
/*global RSVP, SimpleQuery, ComplexQuery*/
(
function
(
jIO
,
RSVP
,
SimpleQuery
,
ComplexQuery
)
{
/**
* JIO Revision Storage.
* It manages document version and can generate conflicts.
* Description:
* {
* "type": "revision",
* "sub_storage": <sub storage description>
* }
*/
// define([module_name], [dependencies], module);
(
function
(
dependencies
,
module
)
{
"
use strict
"
;
if
(
typeof
define
===
'
function
'
&&
define
.
amd
)
{
return
define
(
dependencies
,
module
);
}
module
(
jIO
,
{
hex_sha256
:
hex_sha256
});
}([
'
jio
'
,
'
sha256
'
],
function
(
jIO
,
sha256
)
{
"
use strict
"
;
"
use strict
"
;
var
tool
=
{
// Used to distinguish between operations done within the same millisecond
"
readBlobAsBinaryString
"
:
jIO
.
util
.
readBlobAsBinaryString
,
function
generateUniqueTimestamp
(
time
)
{
"
uniqueJSONStringify
"
:
jIO
.
util
.
uniqueJSONStringify
};
// XXX: replace this with UUIDStorage function call to S4() when it becomes
// publicly accessible
jIO
.
addStorage
(
"
revision
"
,
function
(
spec
)
{
var
uuid
=
(
'
0000
'
+
Math
.
floor
(
Math
.
random
()
*
0x10000
)
.
toString
(
16
)).
slice
(
-
4
),
var
that
=
this
,
priv
=
{};
//timestamp = Date.now().toString();
spec
=
spec
||
{};
timestamp
=
time
.
toString
();
// ATTRIBUTES //
return
timestamp
+
"
-
"
+
uuid
;
priv
.
doc_tree_suffix
=
"
.revision_tree.json
"
;
}
priv
.
sub_storage
=
spec
.
sub_storage
;
// METHODS //
function
isTimestamp
(
id
)
{
/**
//A timestamp is of the form
* Clones an object in deep (without functions)
//"[13 digit number]-[4 numbers/lowercase letters]"
* @method clone
var
re
=
/^
[
0-9
]{13}
-
[
a-z0-9
]{4}
$/
;
* @param {any} object The object to clone
return
re
.
test
(
id
);
* @return {any} The cloned object
}
*/
priv
.
clone
=
function
(
object
)
{
function
removeOldRevs
(
var
tmp
=
JSON
.
stringify
(
object
);
substorage
,
if
(
tmp
===
undefined
)
{
results
,
return
undefined
;
keepDoc
}
)
{
return
JSON
.
parse
(
tmp
);
var
ind
,
};
promises
=
[],
seen
=
{},
/**
docum
,
* Generate a new uuid
log
,
* @method generateUuid
start_ind
,
* @return {string} The new uuid
new_promises
,
*/
doc_id
,
priv
.
generateUuid
=
function
()
{
checkIsId
,
var
S4
=
function
()
{
removeDoc
;
/* 65536 */
for
(
ind
=
0
;
ind
<
results
.
data
.
rows
.
length
;
ind
+=
1
)
{
var
i
,
string
=
Math
.
floor
(
docum
=
results
.
data
.
rows
[
ind
];
Math
.
random
()
*
0x10000
// Count the number of revisions of each document, and delete older
).
toString
(
16
);
// ones.
for
(
i
=
string
.
length
;
i
<
4
;
i
+=
1
)
{
if
(
!
seen
.
hasOwnProperty
(
docum
.
value
.
doc_id
))
{
string
=
'
0
'
+
string
;
seen
[
docum
.
value
.
doc_id
]
=
{
count
:
0
};
}
log
=
seen
[
docum
.
value
.
doc_id
];
log
.
count
+=
1
;
//log.id = docum.id;
// Record the index of the most recent edit that is before the cutoff
if
(
!
log
.
hasOwnProperty
(
"
s
"
)
&&
!
keepDoc
({
doc
:
docum
,
log
:
log
}))
{
log
.
s
=
ind
;
}
// Record the index of the most recent put or remove
if
((
!
log
.
hasOwnProperty
(
"
pr
"
))
&&
(
docum
.
value
.
op
===
"
put
"
||
docum
.
value
.
op
===
"
remove
"
))
{
log
.
pr
=
ind
;
log
.
final
=
ind
;
}
if
((
docum
.
op
===
"
putAttachment
"
||
docum
.
op
===
"
removeAttachment
"
)
&&
log
.
hasOwnProperty
(
docum
.
name
)
&&
!
log
[
docum
.
name
].
hasOwnProperty
(
"
prA
"
))
{
log
[
docum
.
name
].
prA
=
ind
;
log
.
final
=
ind
;
}
}
checkIsId
=
function
(
d
)
{
return
d
.
value
.
doc_id
===
doc_id
;
};
removeDoc
=
function
(
d
)
{
return
substorage
.
remove
(
d
.
id
);
};
for
(
doc_id
in
seen
)
{
if
(
seen
.
hasOwnProperty
(
doc_id
))
{
log
=
seen
[
doc_id
];
start_ind
=
Math
.
max
(
log
.
s
,
log
.
final
+
1
);
new_promises
=
results
.
data
.
rows
.
slice
(
start_ind
)
.
filter
(
checkIsId
)
.
map
(
removeDoc
);
promises
=
promises
.
concat
(
new_promises
);
}
}
return
RSVP
.
all
(
promises
);
}
function
throwCantFindError
(
id
)
{
throw
new
jIO
.
util
.
jIOError
(
"
RevisionStorage: cannot find object '
"
+
id
+
"
'
"
,
404
);
}
}
return
string
;
};
return
S4
()
+
S4
()
+
"
-
"
+
S4
()
+
"
-
"
+
S4
()
+
"
-
"
+
S4
()
+
"
-
"
+
S4
()
+
S4
()
+
S4
();
};
/**
* Generates a hash code of a string
* @method hashCode
* @param {string} string The string to hash
* @return {string} The string hash code
*/
priv
.
hashCode
=
function
(
string
)
{
return
sha256
.
hex_sha256
(
string
);
};
/**
function
throwRemovedError
(
id
)
{
* Checks a revision format
throw
new
jIO
.
util
.
jIOError
(
* @method checkDocumentRevisionFormat
"
RevisionStorage: cannot find object '
"
+
id
+
"
' (removed)
"
,
* @param {object} doc The document object
404
* @return {object} null if ok, else error object
*/
priv
.
checkDocumentRevisionFormat
=
function
(
doc
)
{
var
send_error
=
function
(
message
)
{
return
{
"
status
"
:
409
,
"
message
"
:
message
,
"
reason
"
:
"
Wrong revision
"
};
};
if
(
typeof
doc
.
_rev
===
"
string
"
)
{
if
(
/^
[
0-9
]
+-
[
0-9a-zA-Z
]
+$/
.
test
(
doc
.
_rev
)
===
false
)
{
return
send_error
(
"
The document revision does not match
"
+
"
^[0-9]+-[0-9a-zA-Z]+$
"
);
}
}
if
(
typeof
doc
.
_revs
===
"
object
"
)
{
if
(
typeof
doc
.
_revs
.
start
!==
"
number
"
||
typeof
doc
.
_revs
.
ids
!==
"
object
"
||
typeof
doc
.
_revs
.
ids
.
length
!==
"
number
"
)
{
return
send_error
(
"
The document revision history is not well formated
"
);
);
}
}
}
if
(
typeof
doc
.
_revs_info
===
"
object
"
)
{
if
(
typeof
doc
.
_revs_info
.
length
!==
"
number
"
)
{
return
send_error
(
"
The document revision information
"
+
"
is not well formated
"
);
}
}
};
/**
* Creates a new document tree
* @method newDocTree
* @return {object} The new document tree
*/
priv
.
newDocTree
=
function
()
{
return
{
"
children
"
:
[]};
};
/**
/**
* Convert revs_info to a simple revisions history
* The jIO RevisionStorage extension
* @method revsInfoToHistory
*
* @param {array} revs_info The revs info
* @class RevisionStorage
* @return {object} The revisions history
* @constructor
*/
*/
priv
.
revsInfoToHistory
=
function
(
revs_info
)
{
function
RevisionStorage
(
spec
)
{
var
i
,
revisions
=
{
this
.
_sub_storage
=
jIO
.
createJIO
(
spec
.
sub_storage
);
"
start
"
:
0
,
if
(
spec
.
hasOwnProperty
(
"
include_revisions
"
))
{
"
ids
"
:
[]
this
.
_include_revisions
=
spec
.
include_revisions
;
};
}
else
{
revs_info
=
revs_info
||
[];
this
.
_include_revisions
=
false
;
if
(
revs_info
.
length
>
0
)
{
revisions
.
start
=
parseInt
(
revs_info
[
0
].
rev
.
split
(
'
-
'
)[
0
],
10
);
}
for
(
i
=
0
;
i
<
revs_info
.
length
;
i
+=
1
)
{
revisions
.
ids
.
push
(
revs_info
[
i
].
rev
.
split
(
'
-
'
)[
1
]);
}
}
return
revisions
;
var
substorage
=
this
.
_sub_storage
;
};
this
.
packOldRevisions
=
function
(
save_info
)
{
/**
/**
* Convert the revision history object to an array of revisions.
save_info has this form:
* @method revisionHistoryToList
{
* @param {object} revs The revision history
keep_latest_num: 10,
* @return {array} The revision array
keep_active_revs: timestamp
*/
}
priv
.
revisionHistoryToList
=
function
(
revs
)
{
keep_latest_num = x: keep at most the x latest copies of each unique doc
var
i
,
start
=
revs
.
start
,
new_list
=
[];
keep_active_revs = x: throw away all outdated revisions from before x
for
(
i
=
0
;
i
<
revs
.
ids
.
length
;
i
+=
1
,
start
-=
1
)
{
**/
new_list
.
push
(
start
+
"
-
"
+
revs
.
ids
[
i
]);
var
options
=
{
sort_on
:
[[
"
timestamp
"
,
"
descending
"
]],
select_list
:
[
"
doc
"
,
"
doc_id
"
,
"
op
"
]
},
keep_fixed_num
=
save_info
.
hasOwnProperty
(
"
keep_latest_num
"
);
return
substorage
.
allDocs
(
options
)
.
push
(
function
(
results
)
{
if
(
keep_fixed_num
)
{
return
removeOldRevs
(
substorage
,
results
,
function
(
data
)
{
return
data
.
log
.
count
<=
save_info
.
keep_latest_num
;
});
}
}
return
new_list
;
return
removeOldRevs
(
substorage
,
results
,
function
(
data
)
{
return
data
.
doc
.
id
>
save_info
.
keep_active_revs
;
});
});
};
};
/**
* Convert revision list to revs info.
* @method revisionListToRevsInfo
* @param {array} revision_list The revision list
* @param {object} doc_tree The document tree
* @return {array} The document revs info
*/
priv
.
revisionListToRevsInfo
=
function
(
revision_list
,
doc_tree
)
{
var
revisionListToRevsInfoRec
,
revs_info
=
[],
j
;
for
(
j
=
0
;
j
<
revision_list
.
length
;
j
+=
1
)
{
revs_info
.
push
({
"
rev
"
:
revision_list
[
j
],
"
status
"
:
"
missing
"
});
}
revisionListToRevsInfoRec
=
function
(
index
,
doc_tree
)
{
var
child
,
i
;
if
(
index
<
0
)
{
return
;
}
for
(
i
=
0
;
i
<
doc_tree
.
children
.
length
;
i
+=
1
)
{
child
=
doc_tree
.
children
[
i
];
if
(
child
.
rev
===
revision_list
[
index
])
{
revs_info
[
index
].
status
=
child
.
status
;
revisionListToRevsInfoRec
(
index
-
1
,
child
);
}
}
}
};
revisionListToRevsInfoRec
(
revision_list
.
length
-
1
,
doc_tree
);
return
revs_info
;
};
/**
RevisionStorage
.
prototype
.
get
=
function
(
id_in
)
{
* Update a document metadata revision properties
* @method fillDocumentRevisionProperties
* @param {object} doc The document object
* @param {object} doc_tree The document tree
*/
priv
.
fillDocumentRevisionProperties
=
function
(
doc
,
doc_tree
)
{
if
(
doc
.
_revs_info
)
{
doc
.
_revs
=
priv
.
revsInfoToHistory
(
doc
.
_revs_info
);
}
else
if
(
doc
.
_revs
)
{
doc
.
_revs_info
=
priv
.
revisionListToRevsInfo
(
priv
.
revisionHistoryToList
(
doc
.
_revs
),
doc_tree
);
}
else
if
(
doc
.
_rev
)
{
doc
.
_revs_info
=
priv
.
getRevisionInfo
(
doc
.
_rev
,
doc_tree
);
doc
.
_revs
=
priv
.
revsInfoToHistory
(
doc
.
_revs_info
);
}
else
{
doc
.
_revs_info
=
[];
doc
.
_revs
=
{
"
start
"
:
0
,
"
ids
"
:
[]};
}
if
(
doc
.
_revs
.
start
>
0
)
{
doc
.
_rev
=
doc
.
_revs
.
start
+
"
-
"
+
doc
.
_revs
.
ids
[
0
];
}
else
{
delete
doc
.
_rev
;
}
};
/**
// Query to get the last edit made to this document
* Generates the next revision of a document.
var
substorage
=
this
.
_sub_storage
,
* @methode generateNextRevision
doc_id_query
,
* @param {object} doc The document metadata
metadata_query
,
* @param {boolean} deleted_flag The deleted flag
options
;
* @return {array} 0:The next revision number and 1:the hash code
*/
if
(
this
.
_include_revisions
)
{
priv
.
generateNextRevision
=
function
(
doc
,
deleted_flag
)
{
doc_id_query
=
new
SimpleQuery
({
var
string
,
revision_history
,
revs_info
;
operator
:
"
<=
"
,
doc
=
priv
.
clone
(
doc
)
||
{};
key
:
"
timestamp
"
,
revision_history
=
doc
.
_revs
;
value
:
id_in
revs_info
=
doc
.
_revs_info
;
});
delete
doc
.
_rev
;
}
else
{
delete
doc
.
_revs
;
doc_id_query
=
new
SimpleQuery
({
key
:
"
doc_id
"
,
value
:
id_in
});
delete
doc
.
_revs_info
;
}
string
=
tool
.
uniqueJSONStringify
(
doc
)
+
tool
.
uniqueJSONStringify
(
revision_history
)
+
// Include id_in as value in query object for safety
JSON
.
stringify
(
deleted_flag
?
true
:
false
);
metadata_query
=
new
ComplexQuery
({
revision_history
.
start
+=
1
;
operator
:
"
AND
"
,
revision_history
.
ids
.
unshift
(
priv
.
hashCode
(
string
));
query_list
:
[
doc
.
_revs
=
revision_history
;
doc_id_query
,
doc
.
_rev
=
revision_history
.
start
+
"
-
"
+
revision_history
.
ids
[
0
];
new
ComplexQuery
({
revs_info
.
unshift
({
operator
:
"
OR
"
,
"
rev
"
:
doc
.
_rev
,
query_list
:
[
"
status
"
:
deleted_flag
?
"
deleted
"
:
"
available
"
new
SimpleQuery
({
key
:
"
op
"
,
value
:
"
remove
"
}),
new
SimpleQuery
({
key
:
"
op
"
,
value
:
"
put
"
})
]
})
]
});
});
doc
.
_revs_info
=
revs_info
;
options
=
{
return
doc
;
query
:
metadata_query
,
select_list
:
[
"
op
"
],
limit
:
[
0
,
1
],
sort_on
:
[[
"
timestamp
"
,
"
descending
"
]]
};
};
/**
* Gets the revs info from the document tree
* @method getRevisionInfo
* @param {string} revision The revision to search for
* @param {object} doc_tree The document tree
* @return {array} The revs info
*/
priv
.
getRevisionInfo
=
function
(
revision
,
doc_tree
)
{
var
getRevisionInfoRec
;
getRevisionInfoRec
=
function
(
doc_tree
)
{
var
i
,
child
,
revs_info
;
for
(
i
=
0
;
i
<
doc_tree
.
children
.
length
;
i
+=
1
)
{
child
=
doc_tree
.
children
[
i
];
if
(
child
.
rev
===
revision
)
{
return
[{
"
rev
"
:
child
.
rev
,
"
status
"
:
child
.
status
}];
}
revs_info
=
getRevisionInfoRec
(
child
);
if
(
revs_info
.
length
>
0
||
revision
===
undefined
)
{
revs_info
.
push
({
"
rev
"
:
child
.
rev
,
"
status
"
:
child
.
status
});
return
revs_info
;
}
}
return
[];
};
return
getRevisionInfoRec
(
doc_tree
);
};
priv
.
updateDocumentTree
=
function
(
doc
,
doc_tree
)
{
return
substorage
.
allDocs
(
options
)
var
revs_info
,
updateDocumentTreeRec
;
.
push
(
function
(
results
)
{
doc
=
priv
.
clone
(
doc
);
if
(
results
.
data
.
total_rows
>
0
)
{
revs_info
=
doc
.
_revs_info
;
if
(
results
.
data
.
rows
[
0
].
value
.
op
===
"
put
"
)
{
updateDocumentTreeRec
=
function
(
doc_tree
,
revs_info
)
{
return
substorage
.
get
(
results
.
data
.
rows
[
0
].
id
)
var
i
,
child
,
info
;
.
push
(
function
(
result
)
{
if
(
revs_info
.
length
===
0
)
{
return
result
.
doc
;
return
;
});
}
info
=
revs_info
.
pop
();
for
(
i
=
0
;
i
<
doc_tree
.
children
.
length
;
i
+=
1
)
{
child
=
doc_tree
.
children
[
i
];
if
(
child
.
rev
===
info
.
rev
)
{
return
updateDocumentTreeRec
(
child
,
revs_info
);
}
}
throwRemovedError
(
id_in
);
}
}
doc_tree
.
children
.
unshift
({
throwCantFindError
(
id_in
);
"
rev
"
:
info
.
rev
,
"
status
"
:
info
.
status
,
"
children
"
:
[]
});
});
updateDocumentTreeRec
(
doc_tree
.
children
[
0
],
revs_info
);
};
};
updateDocumentTreeRec
(
doc_tree
,
priv
.
clone
(
revs_info
));
RevisionStorage
.
prototype
.
put
=
function
(
id
,
data
)
{
var
substorage
=
this
.
_sub_storage
,
timestamp
=
generateUniqueTimestamp
(
Date
.
now
()),
metadata
=
{
// XXX: remove this attribute once query can sort_on id
timestamp
:
timestamp
,
doc_id
:
id
,
doc
:
data
,
op
:
"
put
"
};
};
priv
.
send
=
function
(
command
,
method
,
doc
,
option
,
callback
)
{
if
(
this
.
_include_revisions
&&
isTimestamp
(
id
))
{
var
storage
=
command
.
storage
(
priv
.
sub_storage
);
return
substorage
.
get
(
id
)
function
onSuccess
(
success
)
{
.
push
(
function
(
metadata
)
{
callback
(
undefined
,
success
);
metadata
.
timestamp
=
timestamp
;
metadata
.
doc
=
data
;
return
substorage
.
put
(
timestamp
,
metadata
);
},
function
(
error
)
{
if
(
error
.
status_code
===
404
&&
error
instanceof
jIO
.
util
.
jIOError
)
{
return
substorage
.
put
(
timestamp
,
metadata
);
}
}
function
onError
(
err
)
{
throw
error
;
callback
(
err
,
undefined
);
}
);
}
}
if
(
method
===
'
allDocs
'
)
{
return
this
.
_sub_storage
.
put
(
timestamp
,
metadata
);
storage
.
allDocs
(
option
).
then
(
onSuccess
,
onError
);
};
RevisionStorage
.
prototype
.
remove
=
function
(
id
)
{
var
timestamp
=
generateUniqueTimestamp
(
Date
.
now
()
-
1
),
metadata
=
{
// XXX: remove this attribute once query can sort_on id
timestamp
:
timestamp
,
doc_id
:
id
,
op
:
"
remove
"
};
return
this
.
_sub_storage
.
put
(
timestamp
,
metadata
);
};
RevisionStorage
.
prototype
.
allAttachments
=
function
(
id
)
{
var
substorage
=
this
.
_sub_storage
,
query_obj
,
query_removed_check
,
options
,
query_doc_id
,
options_remcheck
,
include_revs
=
this
.
_include_revisions
,
have_seen_id
=
false
;
// id is a timestamp, and allAttachments will return attachment versions
// up-to-and-including those made at time id
if
(
include_revs
)
{
query_doc_id
=
new
SimpleQuery
({
operator
:
"
<=
"
,
key
:
"
timestamp
"
,
value
:
id
});
}
else
{
}
else
{
storage
[
method
](
doc
,
option
).
then
(
onSuccess
,
onError
);
query_doc_id
=
new
SimpleQuery
({
key
:
"
doc_id
"
,
value
:
id
});
}
have_seen_id
=
true
;
};
}
query_removed_check
=
new
ComplexQuery
({
operator
:
"
AND
"
,
query_list
:
[
query_doc_id
,
new
ComplexQuery
({
operator
:
"
OR
"
,
query_list
:
[
new
SimpleQuery
({
key
:
"
op
"
,
value
:
"
put
"
}),
new
SimpleQuery
({
key
:
"
op
"
,
value
:
"
remove
"
})
]
})
]
});
priv
.
getWinnerRevsInfo
=
function
(
doc_tree
)
{
query_obj
=
new
ComplexQuery
({
var
revs_info
=
[],
getWinnerRevsInfoRec
;
operator
:
"
AND
"
,
getWinnerRevsInfoRec
=
function
(
doc_tree
,
tmp_revs_info
)
{
query_list
:
[
var
i
;
query_doc_id
,
if
(
doc_tree
.
rev
)
{
new
ComplexQuery
({
tmp_revs_info
.
unshift
({
operator
:
"
OR
"
,
"
rev
"
:
doc_tree
.
rev
,
query_list
:
[
"
status
"
:
doc_tree
.
status
new
SimpleQuery
({
key
:
"
op
"
,
value
:
"
putAttachment
"
}),
new
SimpleQuery
({
key
:
"
op
"
,
value
:
"
removeAttachment
"
})
]
})
]
});
});
}
if
(
doc_tree
.
children
.
length
===
0
)
{
options_remcheck
=
{
if
(
revs_info
.
length
===
0
||
query
:
query_removed_check
,
(
revs_info
[
0
].
status
!==
"
available
"
&&
select_list
:
[
"
op
"
,
"
timestamp
"
],
tmp_revs_info
[
0
].
status
===
"
available
"
)
||
limit
:
[
0
,
1
],
(
tmp_revs_info
[
0
].
status
===
"
available
"
&&
sort_on
:
[[
"
timestamp
"
,
"
descending
"
]]
revs_info
.
length
<
tmp_revs_info
.
length
))
{
revs_info
=
priv
.
clone
(
tmp_revs_info
);
}
}
for
(
i
=
0
;
i
<
doc_tree
.
children
.
length
;
i
+=
1
)
{
getWinnerRevsInfoRec
(
doc_tree
.
children
[
i
],
tmp_revs_info
);
}
tmp_revs_info
.
shift
();
};
};
getWinnerRevsInfoRec
(
doc_tree
,
[]);
options
=
{
return
revs_info
;
query
:
query_obj
,
sort_on
:
[[
"
timestamp
"
,
"
descending
"
]],
select_list
:
[
"
op
"
,
"
name
"
]
};
};
priv
.
getConflicts
=
function
(
revision
,
doc_tree
)
{
return
this
.
_sub_storage
.
allDocs
(
options_remcheck
)
var
conflicts
=
[],
getConflictsRec
;
// Check the document exists and is not removed
getConflictsRec
=
function
(
doc_tree
)
{
.
push
(
function
(
results
)
{
var
i
;
if
(
results
.
data
.
total_rows
>
0
)
{
if
(
doc_tree
.
rev
===
revision
)
{
if
(
results
.
data
.
rows
[
0
].
id
===
id
)
{
return
;
have_seen_id
=
true
;
}
}
if
(
doc_tree
.
children
.
length
===
0
)
{
if
(
results
.
data
.
rows
[
0
].
value
.
op
===
"
remove
"
)
{
if
(
doc_tree
.
status
!==
"
deleted
"
)
{
throwRemovedError
(
id
);
conflicts
.
push
(
doc_tree
.
rev
);
}
}
}
else
{
throwCantFindError
(
id
);
}
})
.
push
(
function
()
{
return
substorage
.
allDocs
(
options
);
})
.
push
(
function
(
results
)
{
var
seen
=
{},
attachments
=
[],
attachment_promises
=
[],
ind
,
entry
;
// If input mapped to a real timestamp, then the first query result must
// have the inputted id. Otherwise, unexpected results could arise
// by inputting nonsensical strings as id when include_revisions = true
if
(
include_revs
&&
results
.
data
.
total_rows
>
0
&&
results
.
data
.
rows
[
0
].
id
!==
id
&&
!
have_seen_id
)
{
throwCantFindError
(
id
);
}
// Only return attachments if:
// (it is the most recent revision) AND (it is a putAttachment)
attachments
=
results
.
data
.
rows
.
filter
(
function
(
docum
)
{
if
(
!
seen
.
hasOwnProperty
(
docum
.
value
.
name
))
{
var
output
=
(
docum
.
value
.
op
===
"
putAttachment
"
);
seen
[
docum
.
value
.
name
]
=
{};
return
output
;
}
}
for
(
i
=
0
;
i
<
doc_tree
.
children
.
length
;
i
+=
1
)
{
});
getConflictsRec
(
doc_tree
.
children
[
i
]);
// Assembles object of attachment_name: attachment_object
for
(
ind
=
0
;
ind
<
attachments
.
length
;
ind
+=
1
)
{
entry
=
attachments
[
ind
];
attachment_promises
[
entry
.
value
.
name
]
=
substorage
.
getAttachment
(
entry
.
id
,
entry
.
value
.
name
);
}
}
};
return
RSVP
.
hash
(
attachment_promises
);
getConflictsRec
(
doc_tree
);
});
return
conflicts
.
length
===
0
?
undefined
:
conflicts
;
};
priv
.
get
=
function
(
command
,
doc
,
option
,
callback
)
{
priv
.
send
(
command
,
"
get
"
,
doc
,
option
,
callback
);
};
priv
.
put
=
function
(
command
,
doc
,
option
,
callback
)
{
priv
.
send
(
command
,
"
put
"
,
doc
,
option
,
callback
);
};
priv
.
remove
=
function
(
command
,
doc
,
option
,
callback
)
{
priv
.
send
(
command
,
"
remove
"
,
doc
,
option
,
callback
);
};
priv
.
getAttachment
=
function
(
command
,
attachment
,
option
,
callback
)
{
priv
.
send
(
command
,
"
getAttachment
"
,
attachment
,
option
,
callback
);
};
priv
.
putAttachment
=
function
(
command
,
attachment
,
option
,
callback
)
{
priv
.
send
(
command
,
"
putAttachment
"
,
attachment
,
option
,
callback
);
};
priv
.
removeAttachment
=
function
(
command
,
attachment
,
option
,
callback
)
{
priv
.
send
(
command
,
"
removeAttachment
"
,
attachment
,
option
,
callback
);
};
};
priv
.
getDocument
=
function
(
command
,
doc
,
option
,
callback
)
{
RevisionStorage
.
prototype
.
putAttachment
=
function
(
id
,
name
,
blob
)
{
doc
=
priv
.
clone
(
doc
);
var
timestamp
=
generateUniqueTimestamp
(
Date
.
now
()),
doc
.
_id
=
doc
.
_id
+
"
.
"
+
doc
.
_rev
;
metadata
=
{
delete
doc
.
_attachment
;
// XXX: remove this attribute once query can sort_on id
delete
doc
.
_rev
;
timestamp
:
timestamp
,
delete
doc
.
_revs
;
doc_id
:
id
,
delete
doc
.
_revs_info
;
name
:
name
,
priv
.
get
(
command
,
doc
,
option
,
callback
);
op
:
"
putAttachment
"
};
},
priv
.
putDocument
=
function
(
command
,
doc
,
option
,
callback
)
{
substorage
=
this
.
_sub_storage
;
doc
=
priv
.
clone
(
doc
);
doc
.
_id
=
doc
.
_id
+
"
.
"
+
doc
.
_rev
;
delete
doc
.
_attachment
;
delete
doc
.
_data
;
delete
doc
.
_mimetype
;
delete
doc
.
_content_type
;
delete
doc
.
_rev
;
delete
doc
.
_revs
;
delete
doc
.
_revs_info
;
priv
.
put
(
command
,
doc
,
option
,
callback
);
};
priv
.
getRevisionTree
=
function
(
command
,
doc
,
option
,
callback
)
{
if
(
this
.
_include_revisions
&&
isTimestamp
(
id
))
{
doc
=
priv
.
clone
(
doc
);
return
substorage
.
get
(
id
)
doc
.
_id
=
doc
.
_id
+
priv
.
doc_tree_suffix
;
.
push
(
function
(
metadata
)
{
priv
.
get
(
command
,
doc
,
option
,
function
(
err
,
response
)
{
metadata
.
timestamp
=
timestamp
;
if
(
err
)
{
metadata
.
name
=
name
;
return
callback
(
err
,
response
);
},
}
function
(
error
)
{
if
(
response
.
data
&&
response
.
data
.
children
)
{
if
(
!
(
error
.
status_code
===
404
&&
response
.
data
.
children
=
JSON
.
parse
(
response
.
data
.
children
);
error
instanceof
jIO
.
util
.
jIOError
))
{
throw
error
;
}
}
return
callback
(
err
,
response
);
});
});
};
priv
.
getAttachmentList
=
function
(
command
,
doc
,
option
,
callback
)
{
var
attachment_id
,
dealResults
,
state
=
"
ok
"
,
result_list
=
[],
count
=
0
;
dealResults
=
function
(
attachment_id
,
attachment_meta
)
{
return
function
(
err
,
response
)
{
if
(
state
!==
"
ok
"
)
{
return
;
}
count
-=
1
;
if
(
err
)
{
if
(
err
.
status
===
404
)
{
result_list
.
push
(
undefined
);
}
else
{
state
=
"
error
"
;
return
callback
(
err
,
undefined
);
}
}
}
return
this
.
_sub_storage
.
put
(
timestamp
,
metadata
)
result_list
.
push
({
.
push
(
function
()
{
"
_attachment
"
:
attachment_id
,
return
substorage
.
putAttachment
(
timestamp
,
name
,
blob
);
"
_data
"
:
response
.
data
,
"
_content_type
"
:
attachment_meta
.
content_type
});
});
if
(
count
===
0
)
{
state
=
"
finished
"
;
callback
(
undefined
,
{
"
data
"
:
result_list
});
}
};
};
for
(
attachment_id
in
doc
.
_attachments
)
{
if
(
doc
.
_attachments
.
hasOwnProperty
(
attachment_id
))
{
count
+=
1
;
priv
.
getAttachment
(
command
,
{
"
_id
"
:
doc
.
_id
,
"
_attachment
"
:
attachment_id
},
option
,
dealResults
(
attachment_id
,
doc
.
_attachments
[
attachment_id
])
);
}
}
if
(
count
===
0
)
{
callback
(
undefined
,
{
"
data
"
:
[]});
}
};
};
RevisionStorage
.
prototype
.
getAttachment
=
function
(
id
,
name
)
{
priv
.
putAttachmentList
=
function
(
command
,
doc
,
option
,
// In this case, id is a timestamp, so return attachment version at that
attachment_list
,
callback
)
{
// time
var
i
,
dealResults
,
state
=
"
ok
"
,
count
=
0
,
attachment
;
if
(
this
.
_include_revisions
)
{
attachment_list
=
attachment_list
||
[];
return
this
.
_sub_storage
.
getAttachment
(
id
,
name
)
dealResults
=
function
(
)
{
.
push
(
undefined
,
function
(
error
)
{
return
function
(
err
)
{
if
(
error
.
status_code
===
404
&&
if
(
state
!==
"
ok
"
)
{
error
instanceof
jIO
.
util
.
jIOError
)
{
return
;
throwCantFindError
(
id
)
;
}
}
count
-=
1
;
throw
error
;
if
(
err
)
{
});
state
=
"
error
"
;
return
callback
(
err
,
undefined
);
}
if
(
count
===
0
)
{
state
=
"
finished
"
;
callback
(
undefined
,
{});
}
};
};
for
(
i
=
0
;
i
<
attachment_list
.
length
;
i
+=
1
)
{
attachment
=
attachment_list
[
i
];
if
(
attachment
!==
undefined
)
{
count
+=
1
;
attachment
.
_id
=
doc
.
_id
+
"
.
"
+
doc
.
_rev
;
priv
.
putAttachment
(
command
,
attachment
,
option
,
dealResults
(
i
));
}
}
if
(
count
===
0
)
{
return
callback
(
undefined
,
{});
}
}
};
priv
.
putDocumentTree
=
function
(
command
,
doc
,
option
,
doc_tree
,
callback
)
{
// Query to get the last edit made to this document
doc_tree
=
priv
.
clone
(
doc_tree
);
var
substorage
=
this
.
_sub_storage
,
doc_tree
.
_id
=
doc
.
_id
+
priv
.
doc_tree_suffix
;
if
(
doc_tree
.
children
)
{
// "doc_id: id AND
doc_tree
.
children
=
JSON
.
stringify
(
doc_tree
.
children
);
// (op: remove OR ((op: putAttachment OR op: removeAttachment) AND
}
// name: name))"
priv
.
put
(
command
,
doc_tree
,
option
,
callback
);
metadata_query
=
new
ComplexQuery
({
operator
:
"
AND
"
,
query_list
:
[
new
SimpleQuery
({
key
:
"
doc_id
"
,
value
:
id
}),
new
ComplexQuery
({
operator
:
"
OR
"
,
query_list
:
[
new
SimpleQuery
({
key
:
"
op
"
,
value
:
"
remove
"
}),
new
ComplexQuery
({
operator
:
"
AND
"
,
query_list
:
[
new
ComplexQuery
({
operator
:
"
OR
"
,
query_list
:
[
new
SimpleQuery
({
key
:
"
op
"
,
value
:
"
putAttachment
"
}),
new
SimpleQuery
({
key
:
"
op
"
,
value
:
"
removeAttachment
"
})
]
}),
new
SimpleQuery
({
key
:
"
name
"
,
value
:
name
})
]
})
]
})
]
}),
options
=
{
query
:
metadata_query
,
sort_on
:
[[
"
timestamp
"
,
"
descending
"
]],
limit
:
[
0
,
1
],
select_list
:
[
"
op
"
,
"
name
"
]
};
return
substorage
.
allDocs
(
options
)
.
push
(
function
(
results
)
{
if
(
results
.
data
.
total_rows
>
0
)
{
// XXX: issue if attachments are put on a removed document
if
(
results
.
data
.
rows
[
0
].
value
.
op
===
"
remove
"
||
results
.
data
.
rows
[
0
].
value
.
op
===
"
removeAttachment
"
)
{
throwRemovedError
(
id
);
}
return
substorage
.
getAttachment
(
results
.
data
.
rows
[
0
].
id
,
name
);
}
throwCantFindError
(
id
);
});
};
};
priv
.
notFoundError
=
function
(
message
,
reason
)
{
RevisionStorage
.
prototype
.
removeAttachment
=
function
(
id
,
name
)
{
return
{
var
timestamp
=
generateUniqueTimestamp
(
Date
.
now
()),
"
status
"
:
404
,
metadata
=
{
"
message
"
:
message
,
// XXX: remove this attribute once query can sort_on id
"
reason
"
:
reason
timestamp
:
timestamp
,
doc_id
:
id
,
name
:
name
,
op
:
"
removeAttachment
"
};
};
return
this
.
_sub_storage
.
put
(
timestamp
,
metadata
);
};
};
priv
.
conflictError
=
function
(
message
,
reason
)
{
RevisionStorage
.
prototype
.
repair
=
function
()
{
return
{
return
this
.
_sub_storage
.
repair
.
apply
(
this
.
_sub_storage
,
arguments
);
"
status
"
:
409
,
"
message
"
:
message
,
"
reason
"
:
reason
};
};
};
priv
.
revisionGenericRequest
=
function
(
command
,
doc
,
option
,
RevisionStorage
.
prototype
.
hasCapacity
=
function
(
name
)
{
specific_parameter
,
onEnd
)
{
return
name
===
'
list
'
||
var
prev_doc
,
doc_tree
,
attachment_list
,
callback
=
{};
name
===
'
include
'
||
if
(
specific_parameter
.
doc_id
)
{
name
===
'
query
'
||
doc
.
_id
=
specific_parameter
.
doc_id
;
name
===
'
select
'
;
}
if
(
specific_parameter
.
attachment_id
)
{
doc
.
_attachment
=
specific_parameter
.
attachment_id
;
}
callback
.
begin
=
function
()
{
var
check_error
;
doc
.
_id
=
doc
.
_id
||
priv
.
generateUuid
();
// XXX should not generate id
if
(
specific_parameter
.
revision_needed
&&
!
doc
.
_rev
)
{
return
onEnd
(
priv
.
conflictError
(
"
Document update conflict
"
,
"
No document revision was provided
"
),
undefined
);
}
// check revision format
check_error
=
priv
.
checkDocumentRevisionFormat
(
doc
);
if
(
check_error
!==
undefined
)
{
return
onEnd
(
check_error
,
undefined
);
}
priv
.
getRevisionTree
(
command
,
doc
,
option
,
callback
.
getRevisionTree
);
};
callback
.
getRevisionTree
=
function
(
err
,
response
)
{
var
winner_info
,
previous_revision
,
generate_new_revision
;
previous_revision
=
doc
.
_rev
;
generate_new_revision
=
doc
.
_revs
||
doc
.
_revs_info
?
false
:
true
;
if
(
err
)
{
if
(
err
.
status
!==
404
)
{
err
.
message
=
"
Cannot get document revision tree
"
;
return
onEnd
(
err
,
undefined
);
}
}
doc_tree
=
(
response
&&
response
.
data
)
||
priv
.
newDocTree
();
if
(
specific_parameter
.
get
||
specific_parameter
.
getAttachment
)
{
if
(
!
doc
.
_rev
)
{
winner_info
=
priv
.
getWinnerRevsInfo
(
doc_tree
);
if
(
winner_info
.
length
===
0
)
{
return
onEnd
(
priv
.
notFoundError
(
"
Document not found
"
,
"
missing
"
),
undefined
);
}
if
(
winner_info
[
0
].
status
===
"
deleted
"
)
{
return
onEnd
(
priv
.
notFoundError
(
"
Document not found
"
,
"
deleted
"
),
undefined
);
}
doc
.
_rev
=
winner_info
[
0
].
rev
;
}
priv
.
fillDocumentRevisionProperties
(
doc
,
doc_tree
);
return
priv
.
getDocument
(
command
,
doc
,
option
,
callback
.
getDocument
);
}
priv
.
fillDocumentRevisionProperties
(
doc
,
doc_tree
);
if
(
generate_new_revision
)
{
if
(
previous_revision
&&
doc
.
_revs_info
.
length
===
0
)
{
// the document history has changed, it means that the document
// revision was wrong. Add a pseudo history to the document
doc
.
_rev
=
previous_revision
;
doc
.
_revs
=
{
"
start
"
:
parseInt
(
previous_revision
.
split
(
"
-
"
)[
0
],
10
),
"
ids
"
:
[
previous_revision
.
split
(
"
-
"
)[
1
]]
};
doc
.
_revs_info
=
[{
"
rev
"
:
previous_revision
,
"
status
"
:
"
missing
"
}];
}
doc
=
priv
.
generateNextRevision
(
doc
,
specific_parameter
.
remove
);
}
if
(
doc
.
_revs_info
.
length
>
1
)
{
prev_doc
=
{
"
_id
"
:
doc
.
_id
,
"
_rev
"
:
doc
.
_revs_info
[
1
].
rev
};
if
(
!
generate_new_revision
&&
specific_parameter
.
putAttachment
)
{
prev_doc
.
_rev
=
doc
.
_revs_info
[
0
].
rev
;
}
}
// force revs_info status
doc
.
_revs_info
[
0
].
status
=
(
specific_parameter
.
remove
?
"
deleted
"
:
"
available
"
);
priv
.
updateDocumentTree
(
doc
,
doc_tree
);
if
(
prev_doc
)
{
return
priv
.
getDocument
(
command
,
prev_doc
,
option
,
callback
.
getDocument
);
}
if
(
specific_parameter
.
remove
||
specific_parameter
.
removeAttachment
)
{
return
onEnd
(
priv
.
notFoundError
(
"
Unable to remove an inexistent document
"
,
"
missing
"
),
undefined
);
}
priv
.
putDocument
(
command
,
doc
,
option
,
callback
.
putDocument
);
};
callback
.
getDocument
=
function
(
err
,
res_doc
)
{
var
k
,
conflicts
;
if
(
err
)
{
if
(
err
.
status
===
404
)
{
if
(
specific_parameter
.
remove
||
specific_parameter
.
removeAttachment
)
{
return
onEnd
(
priv
.
conflictError
(
"
Document update conflict
"
,
"
Document is missing
"
),
undefined
);
}
if
(
specific_parameter
.
get
)
{
return
onEnd
(
priv
.
notFoundError
(
"
Unable to find the document
"
,
"
missing
"
),
undefined
);
}
res_doc
=
{
"
data
"
:
{}};
}
else
{
err
.
message
=
"
Cannot get document
"
;
return
onEnd
(
err
,
undefined
);
}
}
res_doc
=
res_doc
.
data
;
if
(
specific_parameter
.
get
)
{
res_doc
.
_id
=
doc
.
_id
;
res_doc
.
_rev
=
doc
.
_rev
;
if
(
option
.
conflicts
===
true
)
{
conflicts
=
priv
.
getConflicts
(
doc
.
_rev
,
doc_tree
);
if
(
conflicts
)
{
res_doc
.
_conflicts
=
conflicts
;
}
}
if
(
option
.
revs
===
true
)
{
res_doc
.
_revisions
=
doc
.
_revs
;
}
if
(
option
.
revs_info
===
true
)
{
res_doc
.
_revs_info
=
doc
.
_revs_info
;
}
return
onEnd
(
undefined
,
{
"
data
"
:
res_doc
});
}
if
(
specific_parameter
.
putAttachment
||
specific_parameter
.
removeAttachment
)
{
// copy metadata (not beginning by "_" to document
for
(
k
in
res_doc
)
{
if
(
res_doc
.
hasOwnProperty
(
k
)
&&
!
k
.
match
(
"
^_
"
))
{
doc
[
k
]
=
res_doc
[
k
];
}
}
}
if
(
specific_parameter
.
remove
)
{
priv
.
putDocumentTree
(
command
,
doc
,
option
,
doc_tree
,
callback
.
putDocumentTree
);
}
else
{
priv
.
getAttachmentList
(
command
,
res_doc
,
option
,
callback
.
getAttachmentList
);
}
};
callback
.
getAttachmentList
=
function
(
err
,
res_list
)
{
var
i
,
attachment_found
=
false
;
if
(
err
)
{
err
.
message
=
"
Cannot get attachment
"
;
return
onEnd
(
err
,
undefined
);
}
res_list
=
res_list
.
data
;
attachment_list
=
res_list
||
[];
if
(
specific_parameter
.
getAttachment
)
{
// getting specific attachment
for
(
i
=
0
;
i
<
attachment_list
.
length
;
i
+=
1
)
{
if
(
attachment_list
[
i
]
&&
doc
.
_attachment
===
attachment_list
[
i
].
_attachment
)
{
return
onEnd
(
undefined
,
{
"
data
"
:
attachment_list
[
i
].
_data
});
}
}
return
onEnd
(
priv
.
notFoundError
(
"
Unable to get an inexistent attachment
"
,
"
missing
"
),
undefined
);
}
if
(
specific_parameter
.
remove_from_attachment_list
)
{
// removing specific attachment
for
(
i
=
0
;
i
<
attachment_list
.
length
;
i
+=
1
)
{
if
(
attachment_list
[
i
]
&&
specific_parameter
.
remove_from_attachment_list
.
_attachment
===
attachment_list
[
i
].
_attachment
)
{
attachment_found
=
true
;
attachment_list
[
i
]
=
undefined
;
break
;
}
}
if
(
!
attachment_found
)
{
return
onEnd
(
priv
.
notFoundError
(
"
Unable to remove an inexistent attachment
"
,
"
missing
"
),
undefined
);
}
}
priv
.
putDocument
(
command
,
doc
,
option
,
callback
.
putDocument
);
};
callback
.
putDocument
=
function
(
err
)
{
var
i
,
attachment_found
=
false
;
if
(
err
)
{
err
.
message
=
"
Cannot post the document
"
;
return
onEnd
(
err
,
undefined
);
}
if
(
specific_parameter
.
add_to_attachment_list
)
{
// adding specific attachment
attachment_list
=
attachment_list
||
[];
for
(
i
=
0
;
i
<
attachment_list
.
length
;
i
+=
1
)
{
if
(
attachment_list
[
i
]
&&
specific_parameter
.
add_to_attachment_list
.
_attachment
===
attachment_list
[
i
].
_attachment
)
{
attachment_found
=
true
;
attachment_list
[
i
]
=
specific_parameter
.
add_to_attachment_list
;
break
;
}
}
if
(
!
attachment_found
)
{
attachment_list
.
unshift
(
specific_parameter
.
add_to_attachment_list
);
}
}
priv
.
putAttachmentList
(
command
,
doc
,
option
,
attachment_list
,
callback
.
putAttachmentList
);
};
callback
.
putAttachmentList
=
function
(
err
)
{
if
(
err
)
{
err
.
message
=
"
Cannot copy attacments to the document
"
;
return
onEnd
(
err
,
undefined
);
}
priv
.
putDocumentTree
(
command
,
doc
,
option
,
doc_tree
,
callback
.
putDocumentTree
);
};
callback
.
putDocumentTree
=
function
(
err
)
{
var
response_object
;
if
(
err
)
{
err
.
message
=
"
Cannot update the document history
"
;
return
onEnd
(
err
,
undefined
);
}
response_object
=
{
"
id
"
:
doc
.
_id
,
"
rev
"
:
doc
.
_rev
};
if
(
specific_parameter
.
putAttachment
||
specific_parameter
.
removeAttachment
||
specific_parameter
.
getAttachment
)
{
response_object
.
attachment
=
doc
.
_attachment
;
}
onEnd
(
undefined
,
response_object
);
// if (option.keep_revision_history !== true) {
// // priv.remove(command, prev_doc, option, function () {
// // - change "available" status to "deleted"
// // - remove attachments
// // - done, no callback
// // });
// }
};
callback
.
begin
();
};
};
/**
RevisionStorage
.
prototype
.
buildQuery
=
function
(
options
)
{
* Post the document metadata and create or update a document tree.
// Set default values
* Options:
if
(
options
===
undefined
)
{
options
=
{};
}
* - {boolean} keep_revision_history To keep the previous revisions
if
(
options
.
query
===
undefined
)
{
options
.
query
=
""
;
}
* (false by default) (NYI).
if
(
options
.
sort_on
===
undefined
)
{
options
.
sort_on
=
[];
}
* @method post
if
(
options
.
select_list
===
undefined
)
{
options
.
select_list
=
[];
}
* @param {object} command The JIO command
options
.
query
=
jIO
.
QueryFactory
.
create
(
options
.
query
);
*/
var
meta_options
=
{
that
.
post
=
function
(
command
,
metadata
,
option
)
{
query
:
""
,
priv
.
revisionGenericRequest
(
sort_on
:
[[
"
timestamp
"
,
"
descending
"
]],
command
,
select_list
:
[
"
doc
"
,
"
op
"
,
"
doc_id
"
,
"
timestamp
"
]
metadata
,
},
option
,
include_revs
=
this
.
_include_revisions
;
{},
function
(
err
,
response
)
{
if
(
err
)
{
return
command
.
error
(
err
);
}
command
.
success
({
"
id
"
:
response
.
id
,
"
rev
"
:
response
.
rev
});
}
);
};
/**
if
(
include_revs
)
{
// && options.query.key === "doc_id") {
* Put the document metadata and create or update a document tree.
meta_options
.
query
=
options
.
query
;
* Options:
* - {boolean} keep_revision_history To keep the previous revisions
* (false by default) (NYI).
* @method put
* @param {object} command The JIO command
*/
that
.
put
=
function
(
command
,
metadata
,
option
)
{
priv
.
revisionGenericRequest
(
command
,
metadata
,
option
,
{},
function
(
err
,
response
)
{
if
(
err
)
{
return
command
.
error
(
err
);
}
}
command
.
success
({
"
rev
"
:
response
.
rev
});
}
);
};
return
this
.
_sub_storage
.
allDocs
(
meta_options
)
.
push
(
function
(
results
)
{
results
=
results
.
data
.
rows
;
var
seen
=
{},
docs_to_query
,
i
;
that
.
putAttachment
=
function
(
command
,
param
,
option
)
{
if
(
include_revs
)
{
tool
.
readBlobAsBinaryString
(
param
.
_blob
).
then
(
function
(
event
)
{
param
.
_content_type
=
param
.
_blob
.
type
;
param
.
_data
=
event
.
target
.
result
;
delete
param
.
_blob
;
priv
.
revisionGenericRequest
(
command
,
param
,
option
,
{
"
doc_id
"
:
param
.
_id
,
"
attachment_id
"
:
param
.
_attachment
,
"
add_to_attachment_list
"
:
{
"
_attachment
"
:
param
.
_attachment
,
"
_content_type
"
:
param
.
_content_type
,
"
_data
"
:
param
.
_data
},
"
putAttachment
"
:
true
},
function
(
err
,
response
)
{
if
(
err
)
{
return
command
.
error
(
err
);
}
command
.
success
({
"
rev
"
:
response
.
rev
});
}
);
},
function
()
{
command
.
error
(
"
conflict
"
,
"
broken blob
"
,
"
Cannot read data to put
"
);
});
};
that
.
remove
=
function
(
command
,
param
,
option
)
{
// We only query on versions mapping to puts or putAttachments
priv
.
revisionGenericRequest
(
results
=
results
.
map
(
function
(
docum
,
ind
)
{
command
,
var
data_key
;
param
,
if
(
docum
.
value
.
op
===
"
put
"
)
{
option
,
return
docum
;
{
"
revision_needed
"
:
true
,
"
remove
"
:
true
},
function
(
err
,
response
)
{
if
(
err
)
{
return
command
.
error
(
err
);
}
}
command
.
success
({
"
rev
"
:
response
.
rev
});
if
(
docum
.
value
.
op
===
"
remove
"
)
{
docum
.
value
.
doc
=
{};
return
docum
;
}
}
);
if
(
docum
.
value
.
op
===
"
putAttachment
"
||
};
docum
.
value
.
op
===
"
removeAttachment
"
)
{
that
.
removeAttachment
=
function
(
command
,
param
,
option
)
{
// putAttachment document does not contain doc metadata, so we
priv
.
revisionGenericRequest
(
// add it from the most recent non-removed put on same id
command
,
docum
.
value
.
doc
=
{};
param
,
for
(
i
=
ind
+
1
;
i
<
results
.
length
;
i
+=
1
)
{
option
,
if
(
results
[
i
].
value
.
doc_id
===
docum
.
value
.
doc_id
)
{
{
if
(
results
[
i
].
value
.
op
===
"
put
"
)
{
"
doc_id
"
:
param
.
_id
,
for
(
data_key
in
results
[
i
].
value
.
doc
)
{
"
attachment_id
"
:
param
.
_attachment
,
if
(
results
[
i
].
value
.
doc
.
hasOwnProperty
(
data_key
))
{
"
revision_needed
"
:
true
,
docum
.
value
.
doc
[
data_key
]
=
"
removeAttachment
"
:
true
,
results
[
i
].
value
.
doc
[
data_key
];
"
remove_from_attachment_list
"
:
{
"
_attachment
"
:
param
.
_attachment
}
}
},
function
(
err
,
response
)
{
if
(
err
)
{
return
command
.
error
(
err
);
}
}
command
.
success
({
"
rev
"
:
response
.
rev
})
;
return
docum
;
}
}
);
// If most recent metadata edit before the attachment edit
};
// was a remove, then leave doc empty
if
(
results
[
i
].
value
.
op
===
"
remove
"
)
{
that
.
get
=
function
(
command
,
param
,
option
)
{
return
docum
;
priv
.
revisionGenericRequest
(
command
,
param
,
option
,
{
"
get
"
:
true
},
function
(
err
,
response
)
{
if
(
err
)
{
return
command
.
error
(
err
);
}
}
command
.
success
({
"
data
"
:
response
.
data
});
}
}
);
};
that
.
getAttachment
=
function
(
command
,
param
,
option
)
{
priv
.
revisionGenericRequest
(
command
,
param
,
option
,
{
"
doc_id
"
:
param
.
_id
,
"
attachment_id
"
:
param
.
_attachment
,
"
getAttachment
"
:
true
},
function
(
err
,
response
)
{
if
(
err
)
{
return
command
.
error
(
err
);
}
}
command
.
success
({
"
data
"
:
response
.
data
});
}
}
);
return
false
;
};
});
}
else
{
that
.
allDocs
=
function
(
command
,
param
,
option
)
{
// Only query on latest revisions of non-removed documents/attachment
/*jslint unparam: true */
// edits
var
rows
,
result
=
{
"
total_rows
"
:
0
,
"
rows
"
:
[]},
functions
=
{};
results
=
results
.
map
(
function
(
docum
,
ind
)
{
functions
.
finished
=
0
;
var
data_key
;
functions
.
falseResponseGenerator
=
function
(
response
,
callback
)
{
if
(
docum
.
value
.
op
===
"
put
"
)
{
callback
(
undefined
,
response
);
// Mark as read and include in query
};
if
(
!
seen
.
hasOwnProperty
(
docum
.
value
.
doc_id
))
{
functions
.
fillResultGenerator
=
function
(
doc_id
)
{
seen
[
docum
.
value
.
doc_id
]
=
{};
return
function
(
err
,
doc_tree
)
{
return
docum
;
var
document_revision
,
row
,
revs_info
;
if
(
err
)
{
return
command
.
error
(
err
);
}
doc_tree
=
doc_tree
.
data
;
if
(
typeof
doc_tree
.
children
===
'
string
'
)
{
doc_tree
.
children
=
JSON
.
parse
(
doc_tree
.
children
);
}
revs_info
=
priv
.
getWinnerRevsInfo
(
doc_tree
);
document_revision
=
rows
.
document_revisions
[
doc_id
+
"
.
"
+
revs_info
[
0
].
rev
];
if
(
document_revision
)
{
row
=
{
"
id
"
:
doc_id
,
"
key
"
:
doc_id
,
"
value
"
:
{
"
rev
"
:
revs_info
[
0
].
rev
}
};
if
(
document_revision
.
doc
&&
option
.
include_docs
)
{
document_revision
.
doc
.
_id
=
doc_id
;
document_revision
.
doc
.
_rev
=
revs_info
[
0
].
rev
;
row
.
doc
=
document_revision
.
doc
;
}
}
result
.
rows
.
push
(
row
);
result
.
total_rows
+=
1
;
}
else
if
(
docum
.
value
.
op
===
"
remove
"
||
}
docum
.
value
.
op
===
"
removeAttachment
"
)
{
functions
.
success
();
// Mark as read but do not include in query
};
seen
[
docum
.
value
.
doc_id
]
=
{};
};
functions
.
success
=
function
()
{
}
else
if
(
docum
.
value
.
op
===
"
putAttachment
"
)
{
functions
.
finished
-=
1
;
// If latest edit, mark as read, add document metadata from most
if
(
functions
.
finished
===
0
)
{
// recent put, and add to query
command
.
success
({
"
data
"
:
result
});
if
(
!
seen
.
hasOwnProperty
(
docum
.
value
.
doc_id
))
{
}
seen
[
docum
.
value
.
doc_id
]
=
{};
};
docum
.
value
.
doc
=
{};
priv
.
send
(
command
,
"
allDocs
"
,
null
,
option
,
function
(
err
,
response
)
{
for
(
i
=
ind
+
1
;
i
<
results
.
length
;
i
+=
1
)
{
var
i
,
row
,
selector
,
selected
;
if
(
results
[
i
].
value
.
doc_id
===
docum
.
value
.
doc_id
)
{
if
(
err
)
{
if
(
results
[
i
].
value
.
op
===
"
put
"
)
{
return
command
.
error
(
err
);
for
(
data_key
in
results
[
i
].
value
.
doc
)
{
}
if
(
results
[
i
].
value
.
doc
.
hasOwnProperty
(
data_key
))
{
response
=
response
.
data
;
docum
.
value
.
doc
[
data_key
]
=
selector
=
/
\.
revision_tree
\.
json$/
;
results
[
i
].
value
.
doc
[
data_key
];
rows
=
{
"
revision_trees
"
:
{
// id.revision_tree.json: {
// id: blabla
// doc: {...}
// }
},
"
document_revisions
"
:
{
// id.rev: {
// id: blabla
// rev: 1-1
// doc: {...}
// }
}
}
};
while
(
response
.
rows
.
length
>
0
)
{
// filling rows
row
=
response
.
rows
.
shift
();
selected
=
selector
.
exec
(
row
.
id
);
if
(
selected
)
{
selected
=
selected
.
input
.
substring
(
0
,
selected
.
index
);
// this is a revision tree
rows
.
revision_trees
[
row
.
id
]
=
{
"
id
"
:
selected
};
if
(
row
.
doc
)
{
rows
.
revision_trees
[
row
.
id
].
doc
=
row
.
doc
;
}
}
}
else
{
return
docum
;
// this is a simple revision
rows
.
document_revisions
[
row
.
id
]
=
{
"
id
"
:
row
.
id
.
split
(
"
.
"
).
slice
(
0
,
-
1
),
"
rev
"
:
row
.
id
.
split
(
"
.
"
).
slice
(
-
1
)
};
if
(
row
.
doc
)
{
rows
.
document_revisions
[
row
.
id
].
doc
=
row
.
doc
;
}
}
if
(
results
[
i
].
value
.
op
===
"
remove
"
)
{
// If most recent edit on document was a remove before
// this attachment, then don't include attachment in query
return
false
;
}
}
docum
.
value
.
doc
=
{};
}
}
functions
.
finished
+=
1
;
for
(
i
in
rows
.
revision_trees
)
{
if
(
rows
.
revision_trees
.
hasOwnProperty
(
i
))
{
functions
.
finished
+=
1
;
if
(
rows
.
revision_trees
[
i
].
doc
)
{
functions
.
falseResponseGenerator
(
{
"
data
"
:
rows
.
revision_trees
[
i
].
doc
},
functions
.
fillResultGenerator
(
rows
.
revision_trees
[
i
].
id
)
);
}
else
{
priv
.
getRevisionTree
(
command
,
{
"
_id
"
:
rows
.
revision_trees
[
i
].
id
},
option
,
functions
.
fillResultGenerator
(
rows
.
revision_trees
[
i
].
id
)
);
}
}
}
}
}
}
functions
.
success
()
;
return
false
;
});
});
};
}
docs_to_query
=
results
// XXX
// Filter out all docs flagged as false in previous map call
that
.
check
=
function
(
command
)
{
.
filter
(
function
(
docum
)
{
command
.
success
();
return
docum
;
};
})
// Put into correct format to be passed back to query storage
.
map
(
function
(
docum
)
{
// XXX
if
(
include_revs
)
{
that
.
repair
=
function
(
command
)
{
docum
.
id
=
docum
.
value
.
timestamp
;
command
.
success
();
//docum.doc = docum.value.doc;
}
else
{
docum
.
id
=
docum
.
value
.
doc_id
;
//docum.doc = docum.value.doc;
}
delete
docum
.
value
.
doc_id
;
delete
docum
.
value
.
timestamp
;
delete
docum
.
value
.
op
;
docum
.
value
=
docum
.
value
.
doc
;
//docum.value = {};
return
docum
;
});
return
docs_to_query
;
});
};
};
});
// end RevisionStorage
jIO
.
addStorage
(
'
revision
'
,
RevisionStorage
);
}));
}
(
jIO
,
RSVP
,
SimpleQuery
,
ComplexQuery
));
\ No newline at end of file
test/jio.storage/historystorage.tests.js
deleted
100644 → 0
View file @
a1d4c506
/*jslint nomen: true*/
/*global Blob*/
(
function
(
jIO
,
RSVP
,
Blob
,
QUnit
)
{
"
use strict
"
;
var
test
=
QUnit
.
test
,
stop
=
QUnit
.
stop
,
start
=
QUnit
.
start
,
ok
=
QUnit
.
ok
,
expect
=
QUnit
.
expect
,
deepEqual
=
QUnit
.
deepEqual
,
equal
=
QUnit
.
equal
,
module
=
QUnit
.
module
;
function
putFullDoc
(
storage
,
id
,
doc
,
attachment_name
,
attachment
)
{
return
storage
.
put
(
id
,
doc
)
.
push
(
function
()
{
return
storage
.
putAttachment
(
id
,
attachment_name
,
attachment
);
});
}
module
(
"
HistoryStorage.post
"
,
{
setup
:
function
()
{
// create storage of type "history" with memory as substorage
var
dbname
=
"
db_
"
+
Date
.
now
();
this
.
jio
=
jIO
.
createJIO
({
type
:
"
uuid
"
,
sub_storage
:
{
type
:
"
query
"
,
sub_storage
:
{
type
:
"
history
"
,
sub_storage
:
{
type
:
"
query
"
,
sub_storage
:
{
type
:
"
indexeddb
"
,
database
:
dbname
}
}
}
}
});
this
.
history
=
jIO
.
createJIO
({
type
:
"
uuid
"
,
sub_storage
:
{
type
:
"
query
"
,
sub_storage
:
{
type
:
"
history
"
,
include_revisions
:
true
,
sub_storage
:
{
type
:
"
query
"
,
sub_storage
:
{
type
:
"
indexeddb
"
,
database
:
dbname
}
}
}
}
});
this
.
not_history
=
jIO
.
createJIO
({
type
:
"
query
"
,
sub_storage
:
{
type
:
"
uuid
"
,
sub_storage
:
{
type
:
"
indexeddb
"
,
database
:
dbname
}
}
});
}
});
test
(
"
Verifying simple post works
"
,
function
()
{
stop
();
expect
(
2
);
var
jio
=
this
.
jio
,
history
=
this
.
history
,
not_history
=
this
.
not_history
,
timestamps
;
return
jio
.
post
({
title
:
"
foo0
"
})
.
push
(
function
(
result
)
{
//id = result;
return
jio
.
put
(
result
,
{
title
:
"
foo1
"
});
})
.
push
(
function
(
result
)
{
return
jio
.
get
(
result
);
})
.
push
(
function
(
res
)
{
deepEqual
(
res
,
{
title
:
"
foo1
"
},
"
history storage only retrieves latest version
"
);
})
.
push
(
function
()
{
return
not_history
.
allDocs
({
sort_on
:
[[
"
timestamp
"
,
"
ascending
"
]]
});
})
.
push
(
function
(
results
)
{
timestamps
=
results
.
data
.
rows
.
map
(
function
(
d
)
{
return
d
.
id
;
});
})
.
push
(
function
()
{
return
history
.
allDocs
({
select_list
:
[
"
title
"
]});
})
.
push
(
function
(
res
)
{
deepEqual
(
res
.
data
.
rows
,
[
{
value
:
{
title
:
"
foo1
"
},
doc
:
{},
id
:
timestamps
[
1
]
},
{
value
:
{
title
:
"
foo0
"
},
doc
:
{},
id
:
timestamps
[
0
]
}
],
"
Two revisions logged with correct metadata
"
);
})
.
fail
(
function
(
error
)
{
//console.log(error);
ok
(
false
,
error
);
})
.
always
(
function
()
{
start
();
});
});
/////////////////////////////////////////////////////////////////
// Attachments
/////////////////////////////////////////////////////////////////
module
(
"
HistoryStorage.attachments
"
,
{
setup
:
function
()
{
// create storage of type "history" with memory as substorage
var
dbname
=
"
db_
"
+
Date
.
now
();
this
.
blob1
=
new
Blob
([
'
a
'
]);
this
.
blob2
=
new
Blob
([
'
b
'
]);
this
.
blob3
=
new
Blob
([
'
ccc
'
]);
this
.
other_blob
=
new
Blob
([
'
1
'
]);
this
.
jio
=
jIO
.
createJIO
({
type
:
"
query
"
,
sub_storage
:
{
type
:
"
history
"
,
sub_storage
:
{
type
:
"
query
"
,
sub_storage
:
{
type
:
"
uuid
"
,
sub_storage
:
{
type
:
"
indexeddb
"
,
database
:
dbname
}
}
}
}
});
this
.
history
=
jIO
.
createJIO
({
type
:
"
query
"
,
sub_storage
:
{
type
:
"
history
"
,
include_revisions
:
true
,
sub_storage
:
{
type
:
"
query
"
,
sub_storage
:
{
type
:
"
uuid
"
,
sub_storage
:
{
type
:
"
indexeddb
"
,
database
:
dbname
}
}
}
}
});
this
.
not_history
=
jIO
.
createJIO
({
type
:
"
query
"
,
sub_storage
:
{
type
:
"
uuid
"
,
sub_storage
:
{
type
:
"
indexeddb
"
,
database
:
dbname
}
}
});
}
});
test
(
"
Testing proper adding/removing attachments
"
,
function
()
{
stop
();
expect
(
10
);
var
jio
=
this
.
jio
,
history
=
this
.
history
,
not_history
=
this
.
not_history
,
timestamps
,
blob2
=
this
.
blob2
,
blob1
=
this
.
blob1
,
other_blob
=
this
.
other_blob
,
otherother_blob
=
new
Blob
([
'
abcabc
'
]);
jio
.
put
(
"
doc
"
,
{
title
:
"
foo0
"
})
// 0
.
push
(
function
()
{
return
jio
.
put
(
"
doc2
"
,
{
key
:
"
val
"
});
// 1
})
.
push
(
function
()
{
return
jio
.
putAttachment
(
"
doc
"
,
"
attacheddata
"
,
blob1
);
// 2
})
.
push
(
function
()
{
return
jio
.
putAttachment
(
"
doc
"
,
"
attacheddata
"
,
blob2
);
// 3
})
.
push
(
function
()
{
return
jio
.
putAttachment
(
"
doc
"
,
"
other_attacheddata
"
,
other_blob
);
// 4
})
.
push
(
function
()
{
return
jio
.
putAttachment
(
// 5
"
doc
"
,
"
otherother_attacheddata
"
,
otherother_blob
);
})
.
push
(
function
()
{
return
jio
.
removeAttachment
(
"
doc
"
,
"
otherother_attacheddata
"
);
// 6
})
.
push
(
function
()
{
return
not_history
.
allDocs
({
sort_on
:
[[
"
timestamp
"
,
"
ascending
"
]]
});
})
.
push
(
function
(
results
)
{
timestamps
=
results
.
data
.
rows
.
map
(
function
(
d
)
{
return
d
.
id
;
});
})
.
push
(
function
()
{
return
jio
.
get
(
"
doc
"
);
})
.
push
(
function
(
result
)
{
deepEqual
(
result
,
{
title
:
"
foo0
"
},
"
Get does not return any attachment/revision information
"
);
return
jio
.
getAttachment
(
"
doc
"
,
"
attacheddata
"
);
})
.
push
(
function
(
result
)
{
deepEqual
(
result
,
blob2
,
"
Return the attachment information with getAttachment
"
);
return
history
.
getAttachment
(
timestamps
[
3
],
"
attacheddata
"
);
})
.
push
(
function
(
result
)
{
deepEqual
(
result
,
blob2
,
"
Return the attachment information with getAttachment for
"
+
"
current revision
"
);
return
history
.
getAttachment
(
timestamps
[
2
],
"
attacheddata
"
);
},
function
(
error
)
{
//console.log(error);
ok
(
false
,
error
);
})
.
push
(
function
(
result
)
{
deepEqual
(
result
,
blob1
,
"
Return the attachment information with getAttachment for
"
+
"
previous revision
"
);
return
jio
.
getAttachment
(
timestamps
[
0
],
"
attached
"
);
},
function
(
error
)
{
ok
(
false
,
error
);
})
.
push
(
function
()
{
ok
(
false
,
"
This query should have thrown a 404 error
"
);
},
function
(
error
)
{
ok
(
error
instanceof
jIO
.
util
.
jIOError
,
"
Correct type of error
"
);
deepEqual
(
error
.
status_code
,
404
,
"
Error if you try to go back to a nonexistent timestamp
"
);
deepEqual
(
error
.
message
,
"
HistoryStorage: cannot find object '
"
+
timestamps
[
0
]
+
"
'
"
,
"
Error caught by history storage correctly
"
);
return
jio
.
getAttachment
(
"
doc
"
,
"
other_attacheddata
"
);
})
.
push
(
function
(
result
)
{
deepEqual
(
result
,
other_blob
,
"
Other document successfully queried
"
);
})
.
push
(
function
()
{
return
jio
.
getAttachment
(
"
doc
"
,
"
otherother_attacheddata
"
);
})
.
push
(
function
()
{
ok
(
false
,
"
This query should have thrown a 404 error
"
);
},
function
(
error
)
{
ok
(
error
instanceof
jIO
.
util
.
jIOError
,
"
Correct type of error
"
);
deepEqual
(
error
.
status_code
,
404
,
"
Error if you try to get a removed attachment
"
);
})
.
fail
(
function
(
error
)
{
//console.log(error);
ok
(
false
,
error
);
})
.
always
(
function
()
{
start
();
});
});
test
(
"
get attachment immediately after removing it
"
,
function
()
{
stop
();
expect
(
3
);
var
jio
=
this
.
jio
,
blob1
=
this
.
blob1
;
jio
.
put
(
"
doc
"
,
{
title
:
"
foo0
"
})
.
push
(
function
()
{
return
jio
.
putAttachment
(
"
doc
"
,
"
attacheddata
"
,
blob1
);
})
.
push
(
function
()
{
return
jio
.
removeAttachment
(
"
doc
"
,
"
attacheddata
"
);
})
.
push
(
function
()
{
return
jio
.
getAttachment
(
"
doc
"
,
"
attacheddata
"
);
})
.
push
(
function
()
{
ok
(
false
,
"
This query should have thrown a 404 error
"
);
},
function
(
error
)
{
ok
(
error
instanceof
jIO
.
util
.
jIOError
,
"
throws a jio error
"
);
deepEqual
(
error
.
status_code
,
404
,
"
allAttachments of a removed document throws a 404 error
"
);
deepEqual
(
error
.
message
,
"
HistoryStorage: cannot find object 'doc' (removed)
"
,
"
Error is handled by Historystorage.
"
);
})
.
fail
(
function
(
error
)
{
//console.log(error);
ok
(
false
,
error
);
})
.
always
(
function
()
{
start
();
});
});
test
(
"
Ordering of put and remove attachments is correct
"
,
function
()
{
stop
();
expect
(
1
);
var
jio
=
this
.
jio
,
blob1
=
this
.
blob1
,
blob2
=
this
.
blob2
;
jio
.
put
(
"
doc
"
,
{
title
:
"
foo0
"
})
.
push
(
function
()
{
return
jio
.
putAttachment
(
"
doc
"
,
"
data
"
,
blob1
);
})
.
push
(
function
()
{
return
jio
.
removeAttachment
(
"
doc
"
,
"
data
"
);
})
.
push
(
function
()
{
return
jio
.
putAttachment
(
"
doc
"
,
"
data
"
,
blob2
);
})
.
push
(
function
()
{
return
jio
.
getAttachment
(
"
doc
"
,
"
data
"
);
})
.
push
(
function
(
result
)
{
deepEqual
(
result
,
blob2
,
"
removeAttachment happens before putAttachment
"
);
})
.
fail
(
function
(
error
)
{
//console.log(error);
ok
(
false
,
error
);
})
.
always
(
function
()
{
start
();
});
});
test
(
"
Correctness of allAttachments method on current attachments
"
,
function
()
{
stop
();
expect
(
14
);
var
jio
=
this
.
jio
,
not_history
=
this
.
not_history
,
blob1
=
this
.
blob1
,
blob2
=
this
.
blob2
,
blob3
=
this
.
blob3
,
other_blob
=
this
.
other_blob
;
jio
.
put
(
"
doc
"
,
{
title
:
"
foo0
"
})
.
push
(
function
()
{
return
jio
.
put
(
"
doc2
"
,
{
key
:
"
val
"
});
})
.
push
(
function
()
{
return
jio
.
putAttachment
(
"
doc
"
,
"
attacheddata
"
,
blob1
);
})
.
push
(
function
()
{
return
jio
.
putAttachment
(
"
doc
"
,
"
attacheddata
"
,
blob2
);
})
.
push
(
function
()
{
return
jio
.
putAttachment
(
"
doc
"
,
"
other_attacheddata
"
,
other_blob
);
})
.
push
(
function
()
{
return
jio
.
allAttachments
(
"
doc
"
);
})
.
push
(
function
(
results
)
{
deepEqual
(
results
,
{
"
attacheddata
"
:
blob2
,
"
other_attacheddata
"
:
other_blob
},
"
allAttachments works as expected.
"
);
return
jio
.
removeAttachment
(
"
doc
"
,
"
attacheddata
"
);
//
})
.
push
(
function
()
{
return
jio
.
get
(
"
doc
"
);
})
.
push
(
function
(
result
)
{
deepEqual
(
result
,
{
title
:
"
foo0
"
},
"
Get does not return any attachment information
"
);
return
jio
.
getAttachment
(
"
doc
"
,
"
attacheddata
"
);
})
.
push
(
function
()
{
ok
(
false
,
"
This query should have thrown a 404 error
"
);
},
function
(
error
)
{
ok
(
error
instanceof
jIO
.
util
.
jIOError
,
"
Correct type of error
"
);
deepEqual
(
error
.
status_code
,
404
,
"
Removed attachments cannot be queried (4)
"
);
return
jio
.
allAttachments
(
"
doc
"
);
})
.
push
(
function
(
results
)
{
deepEqual
(
results
,
{
"
other_attacheddata
"
:
blob2
},
"
allAttachments works as expected with a removed attachment
"
);
return
jio
.
putAttachment
(
"
doc
"
,
"
attacheddata
"
,
blob3
);
//
})
.
push
(
function
()
{
return
not_history
.
allDocs
();
})
.
push
(
function
(
results
)
{
var
promises
=
results
.
data
.
rows
.
map
(
function
(
data
)
{
return
not_history
.
get
(
data
.
id
);
});
return
RSVP
.
all
(
promises
);
})
.
push
(
function
(
results
)
{
deepEqual
(
results
,
[
{
timestamp
:
results
[
0
].
timestamp
,
doc_id
:
"
doc
"
,
doc
:
results
[
0
].
doc
,
op
:
"
put
"
},
{
timestamp
:
results
[
1
].
timestamp
,
doc_id
:
"
doc2
"
,
doc
:
results
[
1
].
doc
,
op
:
"
put
"
},
{
timestamp
:
results
[
2
].
timestamp
,
doc_id
:
"
doc
"
,
name
:
"
attacheddata
"
,
op
:
"
putAttachment
"
},
{
timestamp
:
results
[
3
].
timestamp
,
doc_id
:
"
doc
"
,
name
:
"
attacheddata
"
,
op
:
"
putAttachment
"
},
{
timestamp
:
results
[
4
].
timestamp
,
doc_id
:
"
doc
"
,
name
:
"
other_attacheddata
"
,
op
:
"
putAttachment
"
},
{
timestamp
:
results
[
5
].
timestamp
,
doc_id
:
"
doc
"
,
name
:
"
attacheddata
"
,
op
:
"
removeAttachment
"
},
{
timestamp
:
results
[
6
].
timestamp
,
doc_id
:
"
doc
"
,
name
:
"
attacheddata
"
,
op
:
"
putAttachment
"
}
],
"
Other storage can access all document revisions.
"
);
})
.
push
(
function
()
{
return
jio
.
allDocs
();
})
.
push
(
function
(
results
)
{
equal
(
results
.
data
.
total_rows
,
2
,
"
Two documents in accessible storage
"
);
return
jio
.
get
(
results
.
data
.
rows
[
1
].
id
);
})
.
push
(
function
(
result
)
{
deepEqual
(
result
,
{
"
key
"
:
"
val
"
},
"
Get second document accessible from jio storage
"
);
return
not_history
.
allDocs
();
})
.
push
(
function
(
results
)
{
return
RSVP
.
all
(
results
.
data
.
rows
.
map
(
function
(
d
)
{
return
not_history
.
get
(
d
.
id
);
}));
})
.
push
(
function
(
results
)
{
equal
(
results
.
length
,
7
,
"
Seven document revisions in storage
"
);
return
jio
.
remove
(
"
doc
"
);
})
.
push
(
function
()
{
return
jio
.
getAttachment
(
"
doc
"
,
"
attacheddata
"
);
})
.
push
(
function
()
{
ok
(
false
,
"
This query should have thrown a 404 error
"
);
},
function
(
error
)
{
ok
(
error
instanceof
jIO
.
util
.
jIOError
,
"
Correct type of error
"
);
deepEqual
(
error
.
status_code
,
404
,
"
Cannot get the attachment of a removed document
"
);
})
.
push
(
function
()
{
return
jio
.
allAttachments
(
"
doc
"
);
})
.
push
(
function
()
{
ok
(
false
,
"
This query should have thrown a 404 error
"
);
},
function
(
error
)
{
ok
(
error
instanceof
jIO
.
util
.
jIOError
,
"
throws a jio error
"
);
deepEqual
(
error
.
status_code
,
404
,
"
allAttachments of a removed document throws a 404 error
"
);
deepEqual
(
error
.
message
,
"
HistoryStorage: cannot find object 'doc' (removed)
"
,
"
Error is handled by Historystorage.
"
);
})
.
fail
(
function
(
error
)
{
//console.log(error);
ok
(
false
,
error
);
})
.
always
(
function
()
{
start
();
});
});
test
(
"
Correctness of allAttachments method on older revisions
"
,
function
()
{
stop
();
expect
(
11
);
var
jio
=
this
.
jio
,
history
=
this
.
history
,
not_history
=
this
.
not_history
,
blob1
=
new
Blob
([
'
a
'
]),
blob11
=
new
Blob
([
'
ab
'
]),
blob2
=
new
Blob
([
'
abc
'
]),
blob22
=
new
Blob
([
'
abcd
'
]),
timestamps
;
jio
.
put
(
"
doc
"
,
{
title
:
"
foo0
"
})
// 0
.
push
(
function
()
{
return
jio
.
putAttachment
(
"
doc
"
,
"
data
"
,
blob1
);
})
.
push
(
function
()
{
return
jio
.
putAttachment
(
"
doc
"
,
"
data2
"
,
blob2
);
})
.
push
(
function
()
{
return
jio
.
put
(
"
doc
"
,
{
title
:
"
foo1
"
});
// 1
})
.
push
(
function
()
{
return
jio
.
removeAttachment
(
"
doc
"
,
"
data2
"
);
})
.
push
(
function
()
{
return
jio
.
put
(
"
doc
"
,
{
title
:
"
foo2
"
});
// 2
})
.
push
(
function
()
{
return
jio
.
putAttachment
(
"
doc
"
,
"
data
"
,
blob11
);
})
.
push
(
function
()
{
return
jio
.
remove
(
"
doc
"
);
// 3
})
.
push
(
function
()
{
return
jio
.
put
(
"
doc
"
,
{
title
:
"
foo3
"
});
// 4
})
.
push
(
function
()
{
return
jio
.
putAttachment
(
"
doc
"
,
"
data2
"
,
blob22
);
})
.
push
(
function
()
{
return
not_history
.
allDocs
({
query
:
"
op: put OR op: remove
"
,
sort_on
:
[[
"
timestamp
"
,
"
ascending
"
]],
select_list
:
[
"
timestamp
"
]
});
})
.
push
(
function
(
results
)
{
timestamps
=
results
.
data
.
rows
.
map
(
function
(
d
)
{
return
d
.
value
.
timestamp
;
});
})
.
push
(
function
()
{
return
jio
.
allAttachments
(
"
doc
"
);
})
.
push
(
function
(
results
)
{
deepEqual
(
results
,
{
"
data
"
:
blob11
,
"
data2
"
:
blob22
},
"
Current state of document is correct
"
);
return
history
.
allAttachments
(
timestamps
[
0
]);
})
.
push
(
function
(
results
)
{
deepEqual
(
results
,
{},
"
First version of document has 0 attachments
"
);
return
history
.
allAttachments
(
timestamps
[
1
]);
})
.
push
(
function
(
results
)
{
deepEqual
(
results
,
{
data
:
blob1
,
data2
:
blob2
},
"
Both attachments are included in allAttachments
"
);
return
history
.
allAttachments
(
timestamps
[
2
]);
})
.
push
(
function
(
results
)
{
deepEqual
(
results
,
{
data
:
blob1
},
"
Removed attachment does not show up in allAttachments
"
);
return
history
.
allAttachments
(
timestamps
[
3
]);
})
.
push
(
function
()
{
ok
(
false
,
"
This query should have thrown a 404 error
"
);
},
function
(
error
)
{
ok
(
error
instanceof
jIO
.
util
.
jIOError
,
"
throws a jio error
"
);
deepEqual
(
error
.
status_code
,
404
,
"
allAttachments of a removed document throws a 404 error
"
);
deepEqual
(
error
.
message
,
"
HistoryStorage: cannot find object '
"
+
timestamps
[
3
]
+
"
' (removed)
"
,
"
Error is handled by Historystorage.
"
);
})
.
push
(
function
()
{
return
history
.
allAttachments
(
timestamps
[
4
]);
})
.
push
(
function
(
results
)
{
deepEqual
(
results
,
{
data
:
blob11
});
})
.
push
(
function
()
{
return
history
.
allAttachments
(
"
not-a-timestamp-or-doc_id
"
);
})
.
push
(
function
()
{
ok
(
false
,
"
This query should have thrown a 404 error
"
);
},
function
(
error
)
{
ok
(
error
instanceof
jIO
.
util
.
jIOError
,
"
throws a jio error
"
);
deepEqual
(
error
.
status_code
,
404
,
"
allAttachments of a removed document throws a 404 error
"
);
deepEqual
(
error
.
message
,
"
HistoryStorage: cannot find object 'not-a-timestamp-or-doc_id'
"
,
"
Error is handled by Historystorage.
"
);
})
.
fail
(
function
(
error
)
{
//console.log(error);
ok
(
false
,
error
);
})
.
always
(
function
()
{
start
();
});
});
/////////////////////////////////////////////////////////////////
// Querying older revisions
/////////////////////////////////////////////////////////////////
module
(
"
HistoryStorage.get
"
,
{
setup
:
function
()
{
// create storage of type "history" with memory as substorage
var
dbname
=
"
db_
"
+
Date
.
now
();
this
.
jio
=
jIO
.
createJIO
({
type
:
"
query
"
,
sub_storage
:
{
type
:
"
history
"
,
sub_storage
:
{
type
:
"
query
"
,
sub_storage
:
{
type
:
"
uuid
"
,
sub_storage
:
{
type
:
"
indexeddb
"
,
database
:
dbname
}
}
}
}
});
this
.
history
=
jIO
.
createJIO
({
type
:
"
query
"
,
sub_storage
:
{
type
:
"
history
"
,
include_revisions
:
true
,
sub_storage
:
{
type
:
"
query
"
,
sub_storage
:
{
type
:
"
uuid
"
,
sub_storage
:
{
type
:
"
indexeddb
"
,
database
:
dbname
}
}
}
}
});
this
.
not_history
=
jIO
.
createJIO
({
type
:
"
query
"
,
sub_storage
:
{
type
:
"
uuid
"
,
sub_storage
:
{
type
:
"
indexeddb
"
,
database
:
dbname
}
}
});
}
});
test
(
"
Removing documents before putting them
"
,
function
()
{
stop
();
expect
(
4
);
var
jio
=
this
.
jio
;
jio
.
remove
(
"
doc
"
)
.
push
(
function
()
{
return
jio
.
put
(
"
doc2
"
,
{
title
:
"
foo
"
});
})
.
push
(
function
()
{
return
jio
.
get
(
"
doc
"
);
})
.
push
(
function
()
{
ok
(
false
,
"
This statement should not be reached
"
);
},
function
(
error
)
{
ok
(
error
instanceof
jIO
.
util
.
jIOError
,
"
Correct type of error
"
);
deepEqual
(
error
.
status_code
,
404
,
"
Correct status code for getting a non-existent document
"
);
deepEqual
(
error
.
message
,
"
HistoryStorage: cannot find object 'doc' (removed)
"
,
"
Error is handled by history storage before reaching console
"
);
})
.
push
(
function
()
{
return
jio
.
allDocs
({
select_list
:
[
"
title
"
]});
})
.
push
(
function
(
results
)
{
deepEqual
(
results
.
data
.
rows
,
[
{
id
:
"
doc2
"
,
value
:
{
title
:
"
foo
"
},
//timestamp: timestamps[1],
doc
:
{}
}],
"
Document that was removed before being put is not retrieved
"
);
})
.
fail
(
function
(
error
)
{
//console.log(error);
ok
(
false
,
error
);
})
.
always
(
function
()
{
start
();
});
});
test
(
"
Removing documents and then putting them
"
,
function
()
{
stop
();
expect
(
2
);
var
jio
=
this
.
jio
,
history
=
this
.
history
,
timestamps
,
not_history
=
this
.
not_history
;
jio
.
remove
(
"
doc
"
)
.
push
(
function
()
{
return
jio
.
put
(
"
doc
"
,
{
title
:
"
foo
"
});
})
.
push
(
function
()
{
return
jio
.
get
(
"
doc
"
);
})
.
push
(
function
(
result
)
{
deepEqual
(
result
,
{
title
:
"
foo
"
},
"
A put was the most recent edit on 'doc'
"
);
})
.
push
(
function
()
{
return
not_history
.
allDocs
({
sort_on
:
[[
"
timestamp
"
,
"
ascending
"
]]
});
})
.
push
(
function
(
results
)
{
timestamps
=
results
.
data
.
rows
.
map
(
function
(
d
)
{
return
d
.
id
;
});
})
.
push
(
function
()
{
return
history
.
allDocs
({
select_list
:
[
"
title
"
]});
})
.
push
(
function
(
results
)
{
deepEqual
(
results
.
data
.
rows
,
[
{
//id: "doc",
value
:
{
title
:
"
foo
"
},
id
:
timestamps
[
1
],
doc
:
{}
},
{
value
:
{},
id
:
timestamps
[
0
],
doc
:
{}
}],
"
DOcument that was removed before being put is not retrieved
"
);
})
.
fail
(
function
(
error
)
{
//console.log(error);
ok
(
false
,
error
);
})
.
always
(
function
()
{
start
();
});
});
test
(
"
Handling bad input
"
,
function
()
{
stop
();
expect
(
2
);
var
jio
=
this
.
jio
,
history
=
this
.
history
,
not_history
=
this
.
not_history
,
timestamp
;
jio
.
put
(
"
doc
"
,
{
title
:
"
foo
"
})
.
push
(
function
()
{
return
not_history
.
allDocs
();
})
.
push
(
function
(
res
)
{
timestamp
=
res
.
data
.
rows
[
0
].
id
;
return
jio
.
put
(
timestamp
,
{
key
:
"
val
"
});
})
.
push
(
function
()
{
return
jio
.
get
(
"
doc
"
);
})
.
push
(
function
(
result
)
{
deepEqual
(
result
,
{
title
:
"
foo
"
},
"
Saving document with timestamp id does not cause issues (1)
"
);
return
history
.
get
(
timestamp
);
})
.
push
(
function
(
result
)
{
deepEqual
(
result
,
{
title
:
"
foo
"
},
"
Saving document with timestamp id does not cause issues (2)
"
);
return
history
.
get
(
timestamp
);
})
.
fail
(
function
(
error
)
{
//console.log(error);
ok
(
false
,
error
);
})
.
always
(
function
()
{
start
();
});
});
test
(
"
Getting a non-existent document
"
,
function
()
{
stop
();
expect
(
3
);
var
jio
=
this
.
jio
;
jio
.
put
(
"
not_doc
"
,
{})
.
push
(
function
()
{
return
jio
.
get
(
"
doc
"
);
})
.
push
(
function
()
{
ok
(
false
,
"
This statement should not be reached
"
);
},
function
(
error
)
{
//console.log(error);
ok
(
error
instanceof
jIO
.
util
.
jIOError
,
"
Correct type of error
"
);
deepEqual
(
error
.
status_code
,
404
,
"
Correct status code for getting a non-existent document
"
);
deepEqual
(
error
.
message
,
"
HistoryStorage: cannot find object 'doc'
"
,
"
Error is handled by history storage before reaching console
"
);
})
.
fail
(
function
(
error
)
{
//console.log(error);
ok
(
false
,
error
);
})
.
always
(
function
()
{
start
();
});
});
test
(
"
Getting a document with timestamp when include_revisions is false
"
,
function
()
{
stop
();
expect
(
6
);
var
jio
=
this
.
jio
,
not_history
=
this
.
not_history
,
timestamp
;
jio
.
put
(
"
not_doc
"
,
{})
.
push
(
function
()
{
return
jio
.
get
(
"
doc
"
);
})
.
push
(
function
()
{
ok
(
false
,
"
This statement should not be reached
"
);
},
function
(
error
)
{
//console.log(error);
ok
(
error
instanceof
jIO
.
util
.
jIOError
,
"
Correct type of error
"
);
deepEqual
(
error
.
status_code
,
404
,
"
Correct status code for getting a non-existent document
"
);
deepEqual
(
error
.
message
,
"
HistoryStorage: cannot find object 'doc'
"
,
"
Error is handled by history storage before reaching console
"
);
})
.
push
(
function
()
{
return
not_history
.
allDocs
();
})
.
push
(
function
(
results
)
{
timestamp
=
results
.
data
.
rows
[
0
].
id
;
return
jio
.
get
(
timestamp
);
})
.
push
(
function
()
{
ok
(
false
,
"
This statement should not be reached
"
);
},
function
(
error
)
{
//console.log(error);
ok
(
error
instanceof
jIO
.
util
.
jIOError
,
"
Correct type of error
"
);
deepEqual
(
error
.
status_code
,
404
,
"
Correct status code for getting a non-existent document
"
);
deepEqual
(
error
.
message
,
"
HistoryStorage: cannot find object '
"
+
timestamp
+
"
'
"
,
"
Error is handled by history storage before reaching console
"
);
})
.
fail
(
function
(
error
)
{
//console.log(error);
ok
(
false
,
error
);
})
.
always
(
function
()
{
start
();
});
});
test
(
"
Creating a document with put and retrieving it with get
"
,
function
()
{
stop
();
expect
(
5
);
var
jio
=
this
.
jio
,
history
=
this
.
history
,
not_history
=
this
.
not_history
,
timestamps
;
jio
.
put
(
"
doc
"
,
{
title
:
"
version0
"
})
.
push
(
function
()
{
return
not_history
.
allDocs
({
select_list
:
[
"
timestamp
"
]
});
})
.
push
(
function
(
results
)
{
timestamps
=
results
.
data
.
rows
.
map
(
function
(
d
)
{
return
d
.
value
.
timestamp
;
});
})
.
push
(
function
()
{
equal
(
timestamps
.
length
,
1
,
"
One revision is saved in storage
"
);
return
history
.
get
(
timestamps
[
0
]);
})
.
push
(
function
(
result
)
{
deepEqual
(
result
,
{
title
:
"
version0
"
},
"
Get document from history storage
"
);
return
not_history
.
get
(
timestamps
[
0
]
);
})
.
push
(
function
(
result
)
{
deepEqual
(
result
,
{
timestamp
:
timestamps
[
0
],
op
:
"
put
"
,
doc_id
:
"
doc
"
,
doc
:
{
title
:
"
version0
"
}
},
"
Get document from non-history storage
"
);
})
.
push
(
function
()
{
return
jio
.
get
(
"
non-existent-doc
"
);
})
.
push
(
function
()
{
ok
(
false
,
"
This should have thrown an error
"
);
},
function
(
error
)
{
//console.log(error);
ok
(
error
instanceof
jIO
.
util
.
jIOError
,
"
Correct type of error
"
);
deepEqual
(
error
.
status_code
,
404
,
"
Can't access non-existent document
"
);
})
.
fail
(
function
(
error
)
{
//console.log(error);
ok
(
false
,
error
);
})
.
always
(
function
()
{
start
();
});
});
test
(
"
Updating a document with include revisions
"
,
function
()
{
stop
();
expect
(
1
);
var
jio
=
this
.
jio
,
history
=
this
.
history
,
not_history
=
this
.
not_history
,
timestamps
,
t_id
;
jio
.
put
(
"
doc
"
,
{
title
:
"
version0
"
})
.
push
(
function
()
{
return
history
.
put
(
"
doc
"
,
{
title
:
"
version1
"
});
})
.
push
(
function
()
{
return
not_history
.
allDocs
({
sort_on
:
[[
"
timestamp
"
,
"
ascending
"
]]});
})
.
push
(
function
(
results
)
{
t_id
=
results
.
data
.
rows
[
0
].
id
;
return
history
.
put
(
t_id
,
{
title
:
"
version0.1
"
});
})
.
push
(
function
()
{
return
jio
.
put
(
t_id
,
{
title
:
"
label0
"
});
})
.
push
(
function
()
{
return
history
.
put
(
"
1234567891012-abcd
"
,
{
k
:
"
v
"
});
})
.
push
(
function
()
{
return
not_history
.
allDocs
({
select_list
:
[
"
timestamp
"
]
});
})
.
push
(
function
(
results
)
{
timestamps
=
results
.
data
.
rows
.
map
(
function
(
d
)
{
return
d
.
value
.
timestamp
;
});
})
.
push
(
function
()
{
return
not_history
.
allDocs
({
sort_on
:
[[
"
timestamp
"
,
"
ascending
"
]],
select_list
:
[
"
timestamp
"
,
"
op
"
,
"
doc_id
"
,
"
doc
"
]
});
})
.
push
(
function
(
results
)
{
deepEqual
(
results
.
data
.
rows
,
[
{
id
:
timestamps
[
0
],
doc
:
{},
value
:
{
timestamp
:
timestamps
[
0
],
op
:
"
put
"
,
doc_id
:
"
doc
"
,
doc
:
{
title
:
"
version0
"
}
}
},
{
id
:
timestamps
[
1
],
doc
:
{},
value
:
{
timestamp
:
timestamps
[
1
],
op
:
"
put
"
,
doc_id
:
"
doc
"
,
doc
:
{
title
:
"
version1
"
}
}
},
{
id
:
timestamps
[
2
],
doc
:
{},
value
:
{
timestamp
:
timestamps
[
2
],
op
:
"
put
"
,
doc_id
:
"
doc
"
,
doc
:
{
title
:
"
version0.1
"
}
}
},
{
id
:
timestamps
[
3
],
doc
:
{},
value
:
{
timestamp
:
timestamps
[
3
],
op
:
"
put
"
,
doc_id
:
timestamps
[
0
],
doc
:
{
title
:
"
label0
"
}
}
},
{
id
:
timestamps
[
4
],
doc
:
{},
value
:
{
timestamp
:
timestamps
[
4
],
op
:
"
put
"
,
doc_id
:
"
1234567891012-abcd
"
,
doc
:
{
k
:
"
v
"
}
}
}
],
"
Documents stored with correct metadata
"
);
})
.
fail
(
function
(
error
)
{
//console.log(error);
ok
(
false
,
error
);
})
.
always
(
function
()
{
start
();
});
});
test
(
"
Retrieving older revisions with get
"
,
function
()
{
stop
();
expect
(
7
);
var
jio
=
this
.
jio
,
history
=
this
.
history
,
not_history
=
this
.
not_history
,
timestamps
;
return
jio
.
put
(
"
doc
"
,
{
title
:
"
t0
"
,
subtitle
:
"
s0
"
})
.
push
(
function
()
{
return
jio
.
put
(
"
doc
"
,
{
title
:
"
t1
"
,
subtitle
:
"
s1
"
});
})
.
push
(
function
()
{
return
jio
.
put
(
"
doc
"
,
{
title
:
"
t2
"
,
subtitle
:
"
s2
"
});
})
.
push
(
function
()
{
jio
.
remove
(
"
doc
"
);
})
.
push
(
function
()
{
return
jio
.
put
(
"
doc
"
,
{
title
:
"
t3
"
,
subtitle
:
"
s3
"
});
})
.
push
(
function
()
{
return
not_history
.
allDocs
({
select_list
:
[
"
timestamp
"
],
sort_on
:
[[
"
timestamp
"
,
"
ascending
"
]]
});
})
.
push
(
function
(
results
)
{
timestamps
=
results
.
data
.
rows
.
map
(
function
(
d
)
{
return
d
.
value
.
timestamp
;
});
})
.
push
(
function
()
{
return
jio
.
get
(
"
doc
"
);
})
.
push
(
function
(
result
)
{
deepEqual
(
result
,
{
title
:
"
t3
"
,
subtitle
:
"
s3
"
},
"
Get returns latest revision
"
);
return
history
.
get
(
timestamps
[
0
]);
},
function
(
err
)
{
ok
(
false
,
err
);
})
.
push
(
function
(
result
)
{
deepEqual
(
result
,
{
title
:
"
t0
"
,
subtitle
:
"
s0
"
},
"
Get returns first version
"
);
return
history
.
get
(
timestamps
[
1
]);
})
.
push
(
function
(
result
)
{
deepEqual
(
result
,
{
title
:
"
t1
"
,
subtitle
:
"
s1
"
},
"
Get returns second version
"
);
return
history
.
get
(
timestamps
[
2
]);
},
function
(
err
)
{
ok
(
false
,
err
);
})
.
push
(
function
(
result
)
{
deepEqual
(
result
,
{
title
:
"
t2
"
,
subtitle
:
"
s2
"
},
"
Get returns third version
"
);
return
history
.
get
(
timestamps
[
3
]);
},
function
(
err
)
{
ok
(
false
,
err
);
})
.
push
(
function
()
{
ok
(
false
,
"
This should have thrown a 404 error
"
);
return
history
.
get
(
timestamps
[
4
]);
},
function
(
error
)
{
ok
(
error
instanceof
jIO
.
util
.
jIOError
,
"
Correct type of error
"
);
deepEqual
(
error
.
status_code
,
404
,
"
Error if you try to go back more revisions than what exists
"
);
return
history
.
get
(
timestamps
[
4
]);
})
.
push
(
function
(
result
)
{
deepEqual
(
result
,
{
title
:
"
t3
"
,
subtitle
:
"
s3
"
},
"
Get returns latest version
"
);
})
.
fail
(
function
(
error
)
{
//console.log(error);
ok
(
false
,
error
);
})
.
always
(
function
()
{
start
();
});
});
test
(
"
verifying updates correctly when puts are done in parallel
"
,
function
()
{
stop
();
expect
(
8
);
var
jio
=
this
.
jio
,
not_history
=
this
.
not_history
;
jio
.
put
(
"
bar
"
,
{
"
title
"
:
"
foo0
"
})
.
push
(
function
()
{
return
RSVP
.
all
([
jio
.
put
(
"
bar
"
,
{
"
title
"
:
"
foo1
"
}),
jio
.
put
(
"
bar
"
,
{
"
title
"
:
"
foo2
"
}),
jio
.
put
(
"
bar
"
,
{
"
title
"
:
"
foo3
"
}),
jio
.
put
(
"
bar
"
,
{
"
title
"
:
"
foo4
"
}),
jio
.
put
(
"
barbar
"
,
{
"
title
"
:
"
attr0
"
}),
jio
.
put
(
"
barbar
"
,
{
"
title
"
:
"
attr1
"
}),
jio
.
put
(
"
barbar
"
,
{
"
title
"
:
"
attr2
"
}),
jio
.
put
(
"
barbar
"
,
{
"
title
"
:
"
attr3
"
})
]);
})
.
push
(
function
()
{
return
jio
.
get
(
"
bar
"
);
})
.
push
(
function
(
result
)
{
ok
(
result
.
title
!==
"
foo0
"
,
"
Title should have changed from foo0
"
);
})
.
push
(
function
()
{
return
not_history
.
allDocs
({
query
:
""
,
sort_on
:
[[
"
timestamp
"
,
"
ascending
"
]]
});
})
.
push
(
function
(
results
)
{
equal
(
results
.
data
.
total_rows
,
9
,
"
All nine versions exist in storage
"
);
return
not_history
.
get
(
results
.
data
.
rows
[
0
].
id
);
})
.
push
(
function
(
results
)
{
deepEqual
(
results
,
{
doc_id
:
"
bar
"
,
doc
:
{
title
:
"
foo0
"
},
timestamp
:
results
.
timestamp
,
op
:
"
put
"
},
"
The first item in the log is pushing bar's title to 'foo0'
"
);
return
jio
.
remove
(
"
bar
"
);
})
.
push
(
function
()
{
return
jio
.
get
(
"
bar
"
);
})
.
push
(
function
()
{
return
jio
.
get
(
"
barbar
"
);
},
function
(
error
)
{
ok
(
error
instanceof
jIO
.
util
.
jIOError
,
"
Correct type of error
"
);
equal
(
error
.
status_code
,
404
,
"
Correct error status code returned
"
);
return
jio
.
get
(
"
barbar
"
);
})
.
push
(
function
(
result
)
{
ok
(
result
.
title
!==
undefined
,
"
barbar exists and has proper form
"
);
return
not_history
.
allDocs
({
query
:
""
,
sort_on
:
[[
"
op
"
,
"
descending
"
]]
});
})
.
push
(
function
(
results
)
{
equal
(
results
.
data
.
total_rows
,
10
,
"
Remove operation is recorded
"
);
return
not_history
.
get
(
results
.
data
.
rows
[
0
].
id
);
})
.
push
(
function
(
result
)
{
deepEqual
(
result
,
{
doc_id
:
"
bar
"
,
timestamp
:
result
.
timestamp
,
op
:
"
remove
"
});
})
.
fail
(
function
(
error
)
{
//console.log(error);
ok
(
false
,
error
);
})
.
always
(
function
()
{
start
();
});
});
test
(
"
Getting after attachments have been put
"
,
function
()
{
stop
();
expect
(
4
);
var
jio
=
this
.
jio
,
not_history
=
this
.
not_history
,
history
=
this
.
history
,
blob
=
new
Blob
([
'
a
'
]),
edit_log
;
jio
.
put
(
"
doc
"
,
{
"
title
"
:
"
foo0
"
})
.
push
(
function
()
{
return
jio
.
putAttachment
(
"
doc
"
,
"
attachment
"
,
blob
);
})
.
push
(
function
()
{
return
jio
.
removeAttachment
(
"
doc
"
,
"
attachment
"
,
blob
);
})
.
push
(
function
()
{
return
jio
.
get
(
"
doc
"
);
})
.
push
(
function
(
res
)
{
deepEqual
(
res
,
{
title
:
"
foo0
"
},
"
Correct information returned
"
);
return
not_history
.
allDocs
({
select_list
:
[
"
title
"
]});
})
.
push
(
function
(
results
)
{
edit_log
=
results
.
data
.
rows
;
return
history
.
get
(
edit_log
[
0
].
id
);
})
.
push
(
function
(
result
)
{
deepEqual
(
result
,
{
title
:
"
foo0
"
});
return
history
.
get
(
edit_log
[
1
].
id
);
})
.
push
(
function
(
result
)
{
deepEqual
(
result
,
{
title
:
"
foo0
"
});
return
history
.
get
(
edit_log
[
2
].
id
);
})
.
push
(
function
(
result
)
{
deepEqual
(
result
,
{
title
:
"
foo0
"
});
})
.
fail
(
function
(
error
)
{
//console.log(error);
ok
(
false
,
error
);
})
.
always
(
function
()
{
start
();
});
});
/////////////////////////////////////////////////////////////////
// Querying older revisions
/////////////////////////////////////////////////////////////////
module
(
"
HistoryStorage.allDocs
"
,
{
setup
:
function
()
{
// create storage of type "history" with memory as substorage
this
.
dbname
=
"
db_
"
+
Date
.
now
();
this
.
jio
=
jIO
.
createJIO
({
type
:
"
uuid
"
,
sub_storage
:
{
type
:
"
query
"
,
sub_storage
:
{
type
:
"
history
"
,
sub_storage
:
{
type
:
"
query
"
,
sub_storage
:
{
type
:
"
indexeddb
"
,
database
:
this
.
dbname
}
}
}
}
});
this
.
history
=
jIO
.
createJIO
({
type
:
"
uuid
"
,
sub_storage
:
{
type
:
"
query
"
,
sub_storage
:
{
type
:
"
history
"
,
include_revisions
:
true
,
sub_storage
:
{
type
:
"
query
"
,
sub_storage
:
{
type
:
"
indexeddb
"
,
database
:
this
.
dbname
}
}
}
}
});
this
.
not_history
=
jIO
.
createJIO
({
type
:
"
query
"
,
sub_storage
:
{
type
:
"
uuid
"
,
sub_storage
:
{
type
:
"
indexeddb
"
,
database
:
this
.
dbname
}
}
});
}
});
test
(
"
Putting a document and retrieving it with allDocs
"
,
function
()
{
stop
();
expect
(
7
);
var
jio
=
this
.
jio
,
not_history
=
this
.
not_history
,
timestamp
;
jio
.
put
(
"
doc
"
,
{
title
:
"
version0
"
})
.
push
(
function
()
{
return
not_history
.
allDocs
({
query
:
"
doc_id: doc
"
,
select_list
:
[
"
timestamp
"
]
});
})
.
push
(
function
(
results
)
{
timestamp
=
results
.
data
.
rows
[
0
].
value
.
timestamp
;
})
.
push
(
function
()
{
return
RSVP
.
all
([
jio
.
allDocs
(),
jio
.
allDocs
({
query
:
"
title: version0
"
}),
jio
.
allDocs
({
limit
:
[
0
,
1
]}),
jio
.
allDocs
({})
]);
})
.
push
(
function
(
results
)
{
var
ind
=
0
;
for
(
ind
=
0
;
ind
<
results
.
length
-
1
;
ind
+=
1
)
{
deepEqual
(
results
[
ind
],
results
[
ind
+
1
],
"
Each query returns exactly the same correct output
"
);
}
return
results
[
0
];
})
.
push
(
function
(
results
)
{
equal
(
results
.
data
.
total_rows
,
1
,
"
Exactly one result returned
"
);
deepEqual
(
results
.
data
.
rows
[
0
],
{
doc
:
{},
value
:
{},
//timestamp: timestamp,
id
:
"
doc
"
},
"
Correct document format is returned.
"
);
return
not_history
.
allDocs
();
})
.
push
(
function
(
results
)
{
timestamp
=
results
.
data
.
rows
[
0
].
id
;
equal
(
results
.
data
.
total_rows
,
1
,
"
Exactly one result returned
"
);
return
not_history
.
get
(
timestamp
);
})
.
push
(
function
(
result
)
{
deepEqual
(
result
,
{
doc_id
:
"
doc
"
,
doc
:
{
title
:
"
version0
"
},
timestamp
:
timestamp
,
op
:
"
put
"
},
"
When a different type of storage queries historystorage, all
"
+
"
metadata is returned correctly
"
);
})
.
fail
(
function
(
error
)
{
//console.log(error);
ok
(
false
,
error
);
})
.
always
(
function
()
{
start
();
});
});
test
(
"
Putting doc with troublesome properties and retrieving with allDocs
"
,
function
()
{
stop
();
expect
(
1
);
var
jio
=
this
.
jio
;
jio
.
put
(
"
doc
"
,
{
title
:
"
version0
"
,
doc_id
:
"
bar
"
,
_doc_id
:
"
bar2
"
,
timestamp
:
"
foo
"
,
_timestamp
:
"
foo2
"
,
id
:
"
baz
"
,
_id
:
"
baz2
"
,
__id
:
"
baz3
"
,
op
:
"
zop
"
})
.
push
(
function
()
{
return
jio
.
allDocs
({
query
:
"
title: version0 AND _timestamp: >= 0
"
,
select_list
:
[
"
title
"
,
"
doc_id
"
,
"
_doc_id
"
,
"
timestamp
"
,
"
_timestamp
"
,
"
id
"
,
"
_id
"
,
"
__id
"
,
"
op
"
]
});
})
.
push
(
function
(
results
)
{
deepEqual
(
results
.
data
.
rows
,
[
{
doc
:
{},
id
:
"
doc
"
,
//timestamp: timestamp,
value
:
{
title
:
"
version0
"
,
doc_id
:
"
bar
"
,
_doc_id
:
"
bar2
"
,
timestamp
:
"
foo
"
,
_timestamp
:
"
foo2
"
,
id
:
"
baz
"
,
_id
:
"
baz2
"
,
__id
:
"
baz3
"
,
op
:
"
zop
"
}
}],
"
Poorly-named properties are not overwritten in allDocs call
"
);
})
.
fail
(
function
(
error
)
{
//console.log(error);
ok
(
false
,
error
);
})
.
always
(
function
()
{
start
();
});
});
test
(
"
Putting a document, revising it, and retrieving revisions with allDocs
"
,
function
()
{
stop
();
expect
(
10
);
var
jio
=
this
.
jio
,
history
=
this
.
history
,
not_history
=
this
.
not_history
,
timestamps
;
jio
.
put
(
"
doc
"
,
{
title
:
"
version0
"
,
subtitle
:
"
subvers0
"
})
.
push
(
function
()
{
return
jio
.
put
(
"
doc
"
,
{
title
:
"
version1
"
,
subtitle
:
"
subvers1
"
});
})
.
push
(
function
()
{
return
jio
.
put
(
"
doc
"
,
{
title
:
"
version2
"
,
subtitle
:
"
subvers2
"
});
})
.
push
(
function
()
{
return
not_history
.
allDocs
({
select_list
:
[
"
timestamp
"
],
sort_on
:
[[
"
timestamp
"
,
"
ascending
"
]]
});
})
.
push
(
function
(
results
)
{
timestamps
=
results
.
data
.
rows
.
map
(
function
(
d
)
{
return
d
.
value
.
timestamp
;
});
})
.
push
(
function
()
{
return
RSVP
.
all
([
jio
.
allDocs
({
select_list
:
[
"
title
"
,
"
subtitle
"
]}),
jio
.
allDocs
({
query
:
""
,
select_list
:
[
"
title
"
,
"
subtitle
"
]
}),
jio
.
allDocs
({
query
:
"
title: version2
"
,
select_list
:
[
"
title
"
,
"
subtitle
"
]
}),
jio
.
allDocs
({
query
:
"
NOT (title: version1)
"
,
select_list
:
[
"
title
"
,
"
subtitle
"
]
}),
jio
.
allDocs
({
query
:
"
(NOT (subtitle: subvers1)) AND (NOT (title: version0))
"
,
select_list
:
[
"
title
"
,
"
subtitle
"
]
}),
jio
.
allDocs
({
limit
:
[
0
,
1
],
sort_on
:
[[
"
title
"
,
"
ascending
"
]],
select_list
:
[
"
title
"
,
"
subtitle
"
]
})
]);
})
.
push
(
function
(
results
)
{
var
ind
=
0
;
for
(
ind
=
0
;
ind
<
results
.
length
-
1
;
ind
+=
1
)
{
deepEqual
(
results
[
ind
],
results
[
ind
+
1
],
"
Each query returns exactly the same correct output
"
);
}
return
results
[
0
];
})
.
push
(
function
(
results
)
{
equal
(
results
.
data
.
total_rows
,
1
,
"
Exactly one result returned
"
);
deepEqual
(
results
.
data
.
rows
[
0
],
{
value
:
{
title
:
"
version2
"
,
subtitle
:
"
subvers2
"
},
doc
:
{},
//timestamp: timestamps[2],
id
:
"
doc
"
},
"
Correct document format is returned.
"
);
})
.
push
(
function
()
{
return
history
.
allDocs
({
query
:
""
,
select_list
:
[
"
title
"
,
"
subtitle
"
]
});
})
.
push
(
function
(
results
)
{
equal
(
results
.
data
.
total_rows
,
3
,
"
Querying with include_revisions retrieves all versions
"
);
deepEqual
(
results
.
data
.
rows
,
[
{
//id: results.data.rows[0].id,
value
:
{
title
:
"
version2
"
,
subtitle
:
"
subvers2
"
},
id
:
timestamps
[
2
],
doc
:
{}
},
{
//id: results.data.rows[1].id,
value
:
{
title
:
"
version1
"
,
subtitle
:
"
subvers1
"
},
id
:
timestamps
[
1
],
doc
:
{}
},
{
//id: results.data.rows[2].id,
value
:
{
title
:
"
version0
"
,
subtitle
:
"
subvers0
"
},
id
:
timestamps
[
0
],
doc
:
{}
}
],
"
Full version history is included.
"
);
return
not_history
.
allDocs
({
sort_on
:
[[
"
title
"
,
"
ascending
"
]]
});
})
.
push
(
function
(
results
)
{
return
RSVP
.
all
(
results
.
data
.
rows
.
map
(
function
(
d
)
{
return
not_history
.
get
(
d
.
id
);
}));
})
.
push
(
function
(
results
)
{
deepEqual
(
results
,
[
{
timestamp
:
timestamps
[
0
],
op
:
"
put
"
,
doc_id
:
"
doc
"
,
doc
:
{
title
:
"
version0
"
,
subtitle
:
"
subvers0
"
}
},
{
timestamp
:
timestamps
[
1
],
op
:
"
put
"
,
doc_id
:
"
doc
"
,
doc
:
{
title
:
"
version1
"
,
subtitle
:
"
subvers1
"
}
},
{
timestamp
:
timestamps
[
2
],
op
:
"
put
"
,
doc_id
:
"
doc
"
,
doc
:
{
title
:
"
version2
"
,
subtitle
:
"
subvers2
"
}
}
],
"
A different storage type can retrieve all versions as expected.
"
);
})
.
fail
(
function
(
error
)
{
//console.log(error);
ok
(
false
,
error
);
})
.
always
(
function
()
{
start
();
});
});
test
(
"
Putting and removing documents, latest revisions and no removed documents
"
,
function
()
{
stop
();
expect
(
3
);
var
jio
=
this
.
jio
,
not_history
=
this
.
not_history
,
timestamps
;
jio
.
put
(
"
doc_a
"
,
{
title_a
:
"
rev0
"
,
subtitle_a
:
"
subrev0
"
})
.
push
(
function
()
{
return
jio
.
put
(
"
doc_a
"
,
{
title_a
:
"
rev1
"
,
subtitle_a
:
"
subrev1
"
});
})
.
push
(
function
()
{
return
jio
.
put
(
"
doc_b
"
,
{
title_b
:
"
rev0
"
,
subtitle_b
:
"
subrev0
"
});
})
.
push
(
function
()
{
return
jio
.
remove
(
"
doc_b
"
);
})
.
push
(
function
()
{
return
jio
.
put
(
"
doc_c
"
,
{
title_c
:
"
rev0
"
,
subtitle_c
:
"
subrev0
"
});
})
.
push
(
function
()
{
return
jio
.
put
(
"
doc_c
"
,
{
title_c
:
"
rev1
"
,
subtitle_c
:
"
subrev1
"
});
})
.
push
(
function
()
{
return
not_history
.
allDocs
({
sort_on
:
[[
"
timestamp
"
,
"
ascending
"
]]
});
})
.
push
(
function
(
results
)
{
timestamps
=
results
.
data
.
rows
.
map
(
function
(
d
)
{
return
d
.
id
;
});
})
.
push
(
function
()
{
return
jio
.
allDocs
({
sort_on
:
[[
"
timestamp
"
,
"
descending
"
]]});
})
.
push
(
function
(
results
)
{
equal
(
results
.
data
.
total_rows
,
2
,
"
Only two non-removed unique documents exist.
"
);
deepEqual
(
results
.
data
.
rows
,
[
{
id
:
"
doc_c
"
,
value
:
{},
//timestamp: timestamps[5],
doc
:
{}
},
{
id
:
"
doc_a
"
,
value
:
{},
//timestamp: timestamps[1],
doc
:
{}
}
],
"
Empty query returns latest revisions (and no removed documents)
"
);
equal
(
timestamps
.
length
,
6
,
"
Correct number of revisions logged
"
);
})
.
fail
(
function
(
error
)
{
//console.log(error);
ok
(
false
,
error
);
})
.
always
(
function
()
{
start
();
});
}
);
/////////////////////////////////////////////////////////////////
// Complex Queries
/////////////////////////////////////////////////////////////////
test
(
"
More complex query with different options (without revision queries)
"
,
function
()
{
stop
();
expect
(
2
);
var
jio
=
this
.
jio
,
docs
=
[
{
"
date
"
:
1
,
"
type
"
:
"
foo
"
,
"
title
"
:
"
doc
"
},
{
"
date
"
:
2
,
"
type
"
:
"
bar
"
,
"
title
"
:
"
second_doc
"
},
{
"
date
"
:
2
,
"
type
"
:
"
barbar
"
,
"
title
"
:
"
third_doc
"
}
],
blobs
=
[
new
Blob
([
'
a
'
]),
new
Blob
([
'
bcd
'
]),
new
Blob
([
'
eeee
'
])
];
jio
.
put
(
"
doc
"
,
{})
// 0
.
push
(
function
()
{
return
putFullDoc
(
jio
,
"
doc
"
,
docs
[
0
],
"
data
"
,
blobs
[
0
]);
// 1,2
})
.
push
(
function
()
{
return
putFullDoc
(
jio
,
"
second_doc
"
,
docs
[
1
],
"
data
"
,
blobs
[
1
]);
// 3,4
})
.
push
(
function
()
{
return
putFullDoc
(
jio
,
"
third_doc
"
,
docs
[
2
],
"
data
"
,
blobs
[
2
]);
// 5,6
})
.
push
(
function
()
{
return
jio
.
allDocs
({
query
:
"
NOT (date: > 2)
"
,
select_list
:
[
"
date
"
,
"
non-existent-key
"
],
sort_on
:
[[
"
date
"
,
"
ascending
"
],
[
"
non-existent-key
"
,
"
ascending
"
]
]
});
})
.
push
(
function
(
results
)
{
equal
(
results
.
data
.
total_rows
,
3
);
deepEqual
(
results
.
data
.
rows
,
[
{
doc
:
{},
id
:
"
doc
"
,
//timestamp: timestamps[2],
value
:
{
date
:
1
}
},
{
doc
:
{},
id
:
"
third_doc
"
,
//timestamp: timestamps[6],
value
:
{
date
:
2
}
},
{
doc
:
{},
id
:
"
second_doc
"
,
//timestamp: timestamps[4],
value
:
{
date
:
2
}
}
],
"
Query gives correct results in correct order
"
);
})
.
fail
(
function
(
error
)
{
//console.log(error);
ok
(
false
,
error
);
})
.
always
(
function
()
{
start
();
});
});
/////////////////////////////////////////////////////////////////
// Complex Queries with Revision Querying
/////////////////////////////////////////////////////////////////
test
(
"
More complex query with different options (with revision queries)
"
,
function
()
{
stop
();
expect
(
3
);
var
jio
=
this
.
jio
,
history
=
this
.
history
,
not_history
=
this
.
not_history
,
timestamps
,
docs
=
[
{
"
date
"
:
1
,
"
type
"
:
"
foo
"
,
"
title
"
:
"
doc
"
},
{
"
date
"
:
2
,
"
type
"
:
"
bar
"
,
"
title
"
:
"
second_doc
"
}
],
blobs
=
[
new
Blob
([
'
a
'
]),
new
Blob
([
'
bcd
'
]),
new
Blob
([
'
a2
'
]),
new
Blob
([
'
bcd2
'
]),
new
Blob
([
'
a3
'
])
];
jio
.
put
(
"
doc
"
,
{})
// 0
.
push
(
function
()
{
// 1,2
return
putFullDoc
(
jio
,
"
doc
"
,
docs
[
0
],
"
data
"
,
blobs
[
0
]);
})
.
push
(
function
()
{
// 3,4
return
putFullDoc
(
jio
,
"
second_doc
"
,
docs
[
1
],
"
data
"
,
blobs
[
1
]);
})
.
push
(
function
()
{
docs
[
0
].
date
=
4
;
docs
[
0
].
type
=
"
foo2
"
;
docs
[
1
].
date
=
4
;
docs
[
1
].
type
=
"
bar2
"
;
})
.
push
(
function
()
{
// 5,6
return
putFullDoc
(
jio
,
"
doc
"
,
docs
[
0
],
"
data
"
,
blobs
[
2
]);
})
.
push
(
function
()
{
// 7
return
jio
.
remove
(
"
second_doc
"
);
})
.
push
(
function
()
{
// 8,9
return
putFullDoc
(
jio
,
"
second_doc
"
,
docs
[
1
],
"
data
"
,
blobs
[
3
]);
})
.
push
(
function
()
{
return
not_history
.
allDocs
({
sort_on
:
[[
"
timestamp
"
,
"
ascending
"
]]
});
})
.
push
(
function
(
results
)
{
timestamps
=
results
.
data
.
rows
.
map
(
function
(
d
)
{
return
d
.
id
;
});
})
.
push
(
function
()
{
return
not_history
.
allDocs
({
sort_on
:
[[
"
timestamp
"
,
"
descending
"
]],
select_list
:
[
"
op
"
,
"
doc_id
"
,
"
timestamp
"
]
});
})
.
push
(
function
(
results
)
{
deepEqual
(
results
.
data
.
rows
,
[
{
doc
:
{},
id
:
timestamps
[
9
],
value
:
{
"
op
"
:
"
putAttachment
"
,
"
doc_id
"
:
"
second_doc
"
,
"
timestamp
"
:
timestamps
[
9
]
}
},
{
doc
:
{},
id
:
timestamps
[
8
],
value
:
{
"
op
"
:
"
put
"
,
"
doc_id
"
:
"
second_doc
"
,
"
timestamp
"
:
timestamps
[
8
]
}
},
{
doc
:
{},
id
:
timestamps
[
7
],
value
:
{
"
op
"
:
"
remove
"
,
"
doc_id
"
:
"
second_doc
"
,
"
timestamp
"
:
timestamps
[
7
]
}
},
{
doc
:
{},
id
:
timestamps
[
6
],
value
:
{
"
op
"
:
"
putAttachment
"
,
"
doc_id
"
:
"
doc
"
,
"
timestamp
"
:
timestamps
[
6
]
}
},
{
doc
:
{},
id
:
timestamps
[
5
],
value
:
{
"
op
"
:
"
put
"
,
"
doc_id
"
:
"
doc
"
,
"
timestamp
"
:
timestamps
[
5
]
}
},
{
doc
:
{},
id
:
timestamps
[
4
],
value
:
{
"
op
"
:
"
putAttachment
"
,
"
doc_id
"
:
"
second_doc
"
,
"
timestamp
"
:
timestamps
[
4
]
}
},
{
doc
:
{},
id
:
timestamps
[
3
],
value
:
{
"
op
"
:
"
put
"
,
"
doc_id
"
:
"
second_doc
"
,
"
timestamp
"
:
timestamps
[
3
]
}
},
{
doc
:
{},
id
:
timestamps
[
2
],
value
:
{
"
op
"
:
"
putAttachment
"
,
"
doc_id
"
:
"
doc
"
,
"
timestamp
"
:
timestamps
[
2
]
}
},
{
doc
:
{},
id
:
timestamps
[
1
],
value
:
{
"
op
"
:
"
put
"
,
"
doc_id
"
:
"
doc
"
,
"
timestamp
"
:
timestamps
[
1
]
}
},
{
doc
:
{},
id
:
timestamps
[
0
],
value
:
{
"
op
"
:
"
put
"
,
"
doc_id
"
:
"
doc
"
,
"
timestamp
"
:
timestamps
[
0
]
}
}
],
"
All operations are logged correctly
"
);
var
promises
=
results
.
data
.
rows
.
filter
(
function
(
doc
)
{
return
(
doc
.
value
.
op
===
"
put
"
);
})
.
map
(
function
(
data
)
{
return
not_history
.
get
(
data
.
id
);
});
return
RSVP
.
all
(
promises
)
.
then
(
function
(
results
)
{
return
results
.
map
(
function
(
docum
)
{
return
docum
.
doc
;
});
});
})
.
push
(
function
(
results
)
{
deepEqual
(
results
,
[
{
"
date
"
:
4
,
"
type
"
:
"
bar2
"
,
"
title
"
:
"
second_doc
"
},
{
"
date
"
:
4
,
"
type
"
:
"
foo2
"
,
"
title
"
:
"
doc
"
},
{
"
date
"
:
2
,
"
type
"
:
"
bar
"
,
"
title
"
:
"
second_doc
"
},
{
"
date
"
:
1
,
"
type
"
:
"
foo
"
,
"
title
"
:
"
doc
"
},
{}
],
"
All versions of documents are stored correctly
"
);
})
.
push
(
function
()
{
return
history
.
allDocs
({
query
:
"
NOT (date: >= 2 AND date: <= 3) AND
"
+
"
(date: = 1 OR date: = 4)
"
,
select_list
:
[
"
date
"
,
"
non-existent-key
"
,
"
type
"
,
"
title
"
],
sort_on
:
[[
"
date
"
,
"
descending
"
]]
});
})
.
push
(
function
(
results
)
{
deepEqual
(
results
.
data
.
rows
,
[
{
doc
:
{},
id
:
timestamps
[
9
],
value
:
{
date
:
4
,
title
:
"
second_doc
"
,
type
:
"
bar2
"
}
},
{
doc
:
{},
id
:
timestamps
[
8
],
value
:
{
date
:
4
,
title
:
"
second_doc
"
,
type
:
"
bar2
"
}
},
{
doc
:
{},
id
:
timestamps
[
6
],
value
:
{
date
:
4
,
title
:
"
doc
"
,
type
:
"
foo2
"
}
},
{
doc
:
{},
id
:
timestamps
[
5
],
value
:
{
date
:
4
,
title
:
"
doc
"
,
type
:
"
foo2
"
}
},
{
doc
:
{},
id
:
timestamps
[
2
],
value
:
{
date
:
1
,
title
:
"
doc
"
,
type
:
"
foo
"
}
},
{
doc
:
{},
id
:
timestamps
[
1
],
value
:
{
date
:
1
,
title
:
"
doc
"
,
type
:
"
foo
"
}
}
],
"
Query gives correct results in correct order
"
);
})
.
fail
(
function
(
error
)
{
//console.log(error);
ok
(
false
,
error
);
})
.
always
(
function
()
{
start
();
});
});
test
(
"
allDocs with include_revisions with an attachment on a removed document
"
,
function
()
{
stop
();
expect
(
1
);
var
jio
=
this
.
jio
,
history
=
this
.
history
,
not_history
=
this
.
not_history
,
blob
=
new
Blob
([
'
a
'
]),
timestamps
;
jio
.
put
(
"
document
"
,
{
title
:
"
foo
"
})
.
push
(
function
()
{
return
jio
.
remove
(
"
document
"
);
})
.
push
(
function
()
{
return
jio
.
putAttachment
(
"
document
"
,
"
attachment
"
,
blob
);
})
.
push
(
function
()
{
return
not_history
.
allDocs
({
sort_on
:
[[
"
timestamp
"
,
"
ascending
"
]]
});
})
.
push
(
function
(
results
)
{
timestamps
=
results
.
data
.
rows
.
map
(
function
(
d
)
{
return
d
.
id
;
});
})
.
push
(
function
()
{
return
history
.
allDocs
({
select_list
:
[
"
title
"
]});
})
.
push
(
function
(
results
)
{
deepEqual
(
results
.
data
.
rows
,
[
{
id
:
timestamps
[
2
],
doc
:
{},
value
:
{}
},
{
id
:
timestamps
[
1
],
doc
:
{},
value
:
{}
},
{
id
:
timestamps
[
0
],
doc
:
{},
value
:
{
title
:
"
foo
"
}
}],
"
Attachment on removed document is handled correctly
"
);
return
not_history
.
allDocs
({
select_list
:
[
"
doc
"
]});
})
.
fail
(
function
(
error
)
{
//console.log(error);
ok
(
false
,
error
);
})
.
always
(
function
()
{
start
();
});
}
);
test
(
"
allDocs with include_revisions with a removed attachment
"
,
function
()
{
stop
();
expect
(
2
);
var
jio
=
this
.
jio
,
history
=
this
.
history
,
blob
=
new
Blob
([
'
a
'
]),
timestamps
,
not_history
=
this
.
not_history
;
jio
.
put
(
"
document
"
,
{
title
:
"
foo
"
})
.
push
(
function
()
{
return
jio
.
putAttachment
(
"
document
"
,
"
attachment
"
,
blob
);
})
.
push
(
function
()
{
return
jio
.
removeAttachment
(
"
document
"
,
"
attachment
"
);
})
.
push
(
function
()
{
return
not_history
.
allDocs
({
sort_on
:
[[
"
timestamp
"
,
"
ascending
"
]]
});
})
.
push
(
function
(
results
)
{
timestamps
=
results
.
data
.
rows
.
map
(
function
(
d
)
{
return
d
.
id
;
});
})
.
push
(
function
()
{
return
history
.
allDocs
({
select_list
:
[
"
title
"
]});
})
.
push
(
function
(
results
)
{
deepEqual
(
results
.
data
.
rows
,
[
{
id
:
timestamps
[
2
],
doc
:
{},
value
:
{
title
:
"
foo
"
}
},
{
id
:
timestamps
[
1
],
doc
:
{},
value
:
{
title
:
"
foo
"
}
},
{
id
:
timestamps
[
0
],
doc
:
{},
value
:
{
title
:
"
foo
"
}
}],
"
Attachment on removed document is handled correctly
"
);
})
.
push
(
function
()
{
return
jio
.
allAttachments
(
"
document
"
);
})
.
push
(
function
(
results
)
{
deepEqual
(
results
,
{},
"
No non-removed attachments
"
);
})
.
fail
(
function
(
error
)
{
//console.log(error);
ok
(
false
,
error
);
})
.
always
(
function
()
{
start
();
});
});
test
(
"
allDocs with include_revisions only one document
"
,
function
()
{
stop
();
expect
(
1
);
var
jio
=
this
.
jio
,
history
=
this
.
history
,
timestamps
,
not_history
=
this
.
not_history
;
jio
.
put
(
"
doc a
"
,
{
title
:
"
foo0
"
})
.
push
(
function
()
{
return
jio
.
put
(
"
doc a
"
,
{
title
:
"
foo1
"
});
})
.
push
(
function
()
{
return
jio
.
put
(
"
doc b
"
,
{
title
:
"
bar0
"
});
})
.
push
(
function
()
{
return
jio
.
put
(
"
doc b
"
,
{
title
:
"
bar1
"
});
})
.
push
(
function
()
{
return
not_history
.
allDocs
({
sort_on
:
[[
"
timestamp
"
,
"
ascending
"
]]
});
})
.
push
(
function
(
results
)
{
timestamps
=
results
.
data
.
rows
.
map
(
function
(
d
)
{
return
d
.
id
;
});
})
.
push
(
function
()
{
return
history
.
allDocs
({
query
:
'
doc_id: "doc a"
'
,
select_list
:
[
"
title
"
]
});
})
.
push
(
function
(
results
)
{
deepEqual
(
results
.
data
.
rows
,
[
{
id
:
timestamps
[
1
],
doc
:
{},
value
:
{
title
:
"
foo1
"
}
},
{
id
:
timestamps
[
0
],
doc
:
{},
value
:
{
title
:
"
foo0
"
}
}],
"
Only specified document revision history is returned
"
);
})
.
fail
(
function
(
error
)
{
//console.log(error);
ok
(
false
,
error
);
})
.
always
(
function
()
{
start
();
});
});
test
(
"
Parallel edits will not break anything
"
,
function
()
{
stop
();
expect
(
2
);
var
jio
=
this
.
jio
,
history
=
this
.
history
,
blob1
=
new
Blob
([
'
ab
'
]),
blob2
=
new
Blob
([
'
abc
'
]),
blob3
=
new
Blob
([
'
abcd
'
]);
jio
.
put
(
"
doc
"
,
{
k
:
"
v0
"
})
.
push
(
function
()
{
return
RSVP
.
all
([
jio
.
put
(
"
doc
"
,
{
k
:
"
v
"
}),
jio
.
putAttachment
(
"
doc
"
,
"
data
"
,
blob1
),
jio
.
putAttachment
(
"
doc
"
,
"
data2
"
,
blob2
),
jio
.
putAttachment
(
"
doc
"
,
"
data
"
,
blob3
),
jio
.
removeAttachment
(
"
doc
"
,
"
data
"
),
jio
.
removeAttachment
(
"
doc
"
,
"
data2
"
),
jio
.
remove
(
"
doc
"
),
jio
.
remove
(
"
doc
"
),
jio
.
put
(
"
doc
"
,
{
k
:
"
v
"
}),
jio
.
put
(
"
doc
"
,
{
k
:
"
v
"
}),
jio
.
put
(
"
doc2
"
,
{
k
:
"
foo
"
}),
jio
.
remove
(
"
doc
"
),
jio
.
remove
(
"
doc
"
)
]);
})
.
push
(
function
()
{
ok
(
true
,
"
No errors thrown.
"
);
return
history
.
allDocs
();
})
.
push
(
function
(
results
)
{
var
res
=
results
.
data
.
rows
;
equal
(
res
.
length
,
14
,
"
All edits are recorded regardless of ordering
"
);
return
jio
.
allDocs
();
})
.
fail
(
function
(
error
)
{
//console.log(error);
ok
(
false
,
error
);
})
.
always
(
function
()
{
start
();
});
});
test
(
"
Adding second query storage on top of history
"
,
function
()
{
stop
();
expect
(
1
);
var
jio
=
this
.
jio
;
return
jio
.
put
(
"
doca
"
,
{
title
:
"
foo0
"
,
date
:
0
})
.
push
(
function
()
{
return
jio
.
put
(
"
docb
"
,
{
title
:
"
bar0
"
,
date
:
0
});
})
.
push
(
function
()
{
return
jio
.
put
(
"
docb
"
,
{
title
:
"
bar1
"
,
date
:
0
});
})
.
push
(
function
()
{
return
jio
.
put
(
"
doca
"
,
{
title
:
"
foo1
"
,
date
:
1
});
})
.
push
(
function
()
{
return
jio
.
put
(
"
docb
"
,
{
title
:
"
bar2
"
,
date
:
2
});
})
.
push
(
function
()
{
return
jio
.
allDocs
({
query
:
"
title: foo1 OR title: bar2
"
,
select_list
:
[
"
title
"
],
sort_on
:
[[
"
date
"
,
"
ascending
"
]],
limit
:
[
0
,
1
]
});
})
.
push
(
function
(
results
)
{
deepEqual
(
results
.
data
.
rows
,
[
{
doc
:
{},
id
:
"
doca
"
,
value
:
{
title
:
"
foo1
"
}
}
]);
})
.
fail
(
function
(
error
)
{
//console.log(error);
ok
(
false
,
error
);
})
.
always
(
function
()
{
start
();
});
});
module
(
"
HistoryStorage.Full-Example
"
,
{
setup
:
function
()
{
// create storage of type "history" with memory as substorage
var
dbname
=
"
db_
"
+
Date
.
now
();
this
.
blob1
=
new
Blob
([
'
a
'
]);
this
.
blob2
=
new
Blob
([
'
b
'
]);
this
.
blob3
=
new
Blob
([
'
ccc
'
]);
this
.
other_blob
=
new
Blob
([
'
1
'
]);
this
.
jio
=
jIO
.
createJIO
({
type
:
"
query
"
,
sub_storage
:
{
type
:
"
history
"
,
sub_storage
:
{
type
:
"
query
"
,
sub_storage
:
{
type
:
"
uuid
"
,
sub_storage
:
{
type
:
"
indexeddb
"
,
database
:
dbname
}
}
}
}
});
this
.
history
=
jIO
.
createJIO
({
type
:
"
query
"
,
sub_storage
:
{
type
:
"
history
"
,
include_revisions
:
true
,
sub_storage
:
{
type
:
"
query
"
,
sub_storage
:
{
type
:
"
uuid
"
,
sub_storage
:
{
type
:
"
indexeddb
"
,
database
:
dbname
}
}
}
}
});
this
.
not_history
=
jIO
.
createJIO
({
type
:
"
query
"
,
sub_storage
:
{
type
:
"
uuid
"
,
sub_storage
:
{
type
:
"
indexeddb
"
,
database
:
dbname
}
}
});
}
});
test
(
"
Retrieving history with attachments
"
,
function
()
{
stop
();
expect
(
1
);
var
jio
=
this
.
jio
,
history
=
this
.
history
,
timestamps
,
not_history
=
this
.
not_history
,
blobs1
=
[
new
Blob
([
'
a
'
]),
new
Blob
([
'
ab
'
]),
new
Blob
([
'
abc
'
]),
new
Blob
([
'
abcd
'
]),
new
Blob
([
'
abcde
'
])
],
blobs2
=
[
new
Blob
([
'
abcdef
'
]),
new
Blob
([
'
abcdefg
'
]),
new
Blob
([
'
abcdefgh
'
]),
new
Blob
([
'
abcdefghi
'
]),
new
Blob
([
'
abcdefghij
'
])
];
putFullDoc
(
jio
,
"
doc
"
,
{
title
:
"
bar
"
},
"
data
"
,
blobs1
[
0
])
.
push
(
function
()
{
return
putFullDoc
(
jio
,
"
doc
"
,
{
title
:
"
bar0
"
},
"
data
"
,
blobs1
[
1
]);
})
.
push
(
function
()
{
return
putFullDoc
(
jio
,
"
doc
"
,
{
title
:
"
bar1
"
},
"
data
"
,
blobs1
[
2
]);
})
.
push
(
function
()
{
return
putFullDoc
(
jio
,
"
doc2
"
,
{
title
:
"
foo0
"
},
"
data
"
,
blobs2
[
0
]);
})
.
push
(
function
()
{
return
putFullDoc
(
jio
,
"
doc2
"
,
{
title
:
"
foo1
"
},
"
data
"
,
blobs2
[
0
]);
})
.
push
(
function
()
{
return
putFullDoc
(
jio
,
"
doc
"
,
{
title
:
"
bar2
"
},
"
data
"
,
blobs1
[
3
]);
})
.
push
(
function
()
{
return
putFullDoc
(
jio
,
"
doc
"
,
{
title
:
"
bar3
"
},
"
data
"
,
blobs1
[
4
]);
})
.
push
(
function
()
{
return
not_history
.
allDocs
({
sort_on
:
[[
"
timestamp
"
,
"
ascending
"
]]
});
})
.
push
(
function
(
results
)
{
timestamps
=
results
.
data
.
rows
.
map
(
function
(
d
)
{
return
d
.
id
;
});
})
.
push
(
function
()
{
return
history
.
allDocs
({
select_list
:
[
"
title
"
]
});
})
.
push
(
function
(
results
)
{
deepEqual
(
results
.
data
.
rows
,
[
{
doc
:
{},
id
:
timestamps
[
13
],
value
:
{
title
:
"
bar3
"
}
},
{
doc
:
{},
id
:
timestamps
[
12
],
value
:
{
title
:
"
bar3
"
}
},
{
doc
:
{},
id
:
timestamps
[
11
],
value
:
{
title
:
"
bar2
"
}
},
{
doc
:
{},
id
:
timestamps
[
10
],
value
:
{
title
:
"
bar2
"
}
},
{
doc
:
{},
id
:
timestamps
[
9
],
value
:
{
title
:
"
foo1
"
}
},
{
doc
:
{},
id
:
timestamps
[
8
],
value
:
{
title
:
"
foo1
"
}
},
{
doc
:
{},
id
:
timestamps
[
7
],
value
:
{
title
:
"
foo0
"
}
},
{
doc
:
{},
id
:
timestamps
[
6
],
value
:
{
title
:
"
foo0
"
}
},
{
doc
:
{},
id
:
timestamps
[
5
],
value
:
{
title
:
"
bar1
"
}
},
{
doc
:
{},
id
:
timestamps
[
4
],
value
:
{
title
:
"
bar1
"
}
},
{
doc
:
{},
id
:
timestamps
[
3
],
value
:
{
title
:
"
bar0
"
}
},
{
doc
:
{},
id
:
timestamps
[
2
],
value
:
{
title
:
"
bar0
"
}
},
{
doc
:
{},
id
:
timestamps
[
1
],
value
:
{
title
:
"
bar
"
}
},
{
doc
:
{},
id
:
timestamps
[
0
],
value
:
{
title
:
"
bar
"
}
}
],
"
allDocs with include_revisions should return all revisions
"
);
})
.
fail
(
function
(
error
)
{
//console.log(error);
ok
(
false
,
error
);
})
.
always
(
function
()
{
start
();
});
});
test
(
"
Retrieving history with attachments with less straightforward ordering
"
,
function
()
{
stop
();
expect
(
1
);
var
jio
=
this
.
jio
,
history
=
this
.
history
,
not_history
=
this
.
not_history
,
timestamps
,
blobs1
=
[
new
Blob
([
'
a
'
]),
new
Blob
([
'
ab
'
]),
new
Blob
([
'
abc
'
]),
new
Blob
([
'
abcd
'
]),
new
Blob
([
'
abcde
'
])
];
jio
.
put
(
"
doc
"
,
{
title
:
"
bar
"
})
.
push
(
function
()
{
return
jio
.
put
(
"
doc
"
,
{
title
:
"
bar0
"
});
})
.
push
(
function
()
{
return
jio
.
putAttachment
(
"
doc
"
,
"
data
"
,
blobs1
[
0
]);
})
.
push
(
function
()
{
return
jio
.
put
(
"
doc2
"
,
{
title
:
"
foo0
"
});
})
.
push
(
function
()
{
return
jio
.
putAttachment
(
"
doc
"
,
"
data
"
,
blobs1
[
1
]);
})
.
push
(
function
()
{
return
not_history
.
allDocs
({
sort_on
:
[[
"
timestamp
"
,
"
ascending
"
]]
});
})
.
push
(
function
(
results
)
{
timestamps
=
results
.
data
.
rows
.
map
(
function
(
d
)
{
return
d
.
id
;
});
})
.
push
(
function
()
{
return
history
.
allDocs
({
select_list
:
[
"
title
"
]
});
})
.
push
(
function
(
results
)
{
deepEqual
(
results
.
data
.
rows
,
[
{
doc
:
{},
id
:
timestamps
[
4
],
value
:
{
title
:
"
bar0
"
}
},
{
doc
:
{},
id
:
timestamps
[
3
],
value
:
{
title
:
"
foo0
"
}
},
{
doc
:
{},
id
:
timestamps
[
2
],
value
:
{
title
:
"
bar0
"
}
},
{
doc
:
{},
id
:
timestamps
[
1
],
value
:
{
title
:
"
bar0
"
}
},
{
doc
:
{},
id
:
timestamps
[
0
],
value
:
{
title
:
"
bar
"
}
}
],
"
allDocs with include_revisions should return all revisions
"
);
})
.
fail
(
function
(
error
)
{
//console.log(error);
ok
(
false
,
error
);
})
.
always
(
function
()
{
start
();
});
});
test
(
"
Retrieving history with attachments with removals
"
,
function
()
{
stop
();
expect
(
2
);
var
jio
=
this
.
jio
,
history
=
this
.
history
,
not_history
=
this
.
not_history
,
timestamps
,
blobs1
=
[
new
Blob
([
'
a
'
]),
new
Blob
([
'
ab
'
]),
new
Blob
([
'
abc
'
]),
new
Blob
([
'
abcd
'
]),
new
Blob
([
'
abcde
'
])
];
jio
.
put
(
"
doc
"
,
{
title
:
"
bar
"
})
.
push
(
function
()
{
return
jio
.
put
(
"
doc
"
,
{
title
:
"
bar0
"
});
})
.
push
(
function
()
{
return
jio
.
putAttachment
(
"
doc
"
,
"
data
"
,
blobs1
[
0
]);
})
.
push
(
function
()
{
return
jio
.
put
(
"
doc2
"
,
{
title
:
"
foo0
"
});
})
.
push
(
function
()
{
return
jio
.
putAttachment
(
"
doc
"
,
"
data
"
,
blobs1
[
1
]);
})
.
push
(
function
()
{
return
jio
.
allDocs
({
select_list
:
[
"
title
"
]
});
})
.
push
(
function
(
results
)
{
deepEqual
(
results
.
data
.
rows
,
[
{
doc
:
{},
id
:
"
doc
"
,
//timestamp: timestamps[4],
value
:
{
title
:
"
bar0
"
}
},
{
doc
:
{},
id
:
"
doc2
"
,
//timestamp: timestamps[3],
value
:
{
title
:
"
foo0
"
}
}
],
"
allDocs with include_revisions false should return all revisions
"
);
})
.
push
(
function
()
{
return
not_history
.
allDocs
({
sort_on
:
[[
"
timestamp
"
,
"
ascending
"
]]
});
})
.
push
(
function
(
results
)
{
timestamps
=
results
.
data
.
rows
.
map
(
function
(
d
)
{
return
d
.
id
;
});
})
.
push
(
function
()
{
return
history
.
allDocs
({
select_list
:
[
"
title
"
]
});
})
.
push
(
function
(
results
)
{
deepEqual
(
results
.
data
.
rows
,
[
{
doc
:
{},
id
:
timestamps
[
4
],
value
:
{
title
:
"
bar0
"
}
},
{
doc
:
{},
id
:
timestamps
[
3
],
value
:
{
title
:
"
foo0
"
}
},
{
doc
:
{},
id
:
timestamps
[
2
],
value
:
{
title
:
"
bar0
"
}
},
{
doc
:
{},
id
:
timestamps
[
1
],
value
:
{
title
:
"
bar0
"
}
},
{
doc
:
{},
id
:
timestamps
[
0
],
value
:
{
title
:
"
bar
"
}
}
],
"
allDocs with include_revisions true should return all revisions
"
);
})
.
fail
(
function
(
error
)
{
//console.log(error);
ok
(
false
,
error
);
})
.
always
(
function
()
{
start
();
});
});
module
(
"
HistoryStorage.pack
"
,
{
setup
:
function
()
{
// create storage of type "history" with memory as substorage
var
dbname
=
"
db_
"
+
Date
.
now
();
this
.
jio
=
jIO
.
createJIO
({
type
:
"
uuid
"
,
sub_storage
:
{
type
:
"
query
"
,
sub_storage
:
{
type
:
"
history
"
,
sub_storage
:
{
type
:
"
query
"
,
sub_storage
:
{
type
:
"
indexeddb
"
,
database
:
dbname
}
}
}
}
});
this
.
history
=
jIO
.
createJIO
({
type
:
"
uuid
"
,
sub_storage
:
{
type
:
"
query
"
,
sub_storage
:
{
type
:
"
history
"
,
include_revisions
:
true
,
sub_storage
:
{
type
:
"
query
"
,
sub_storage
:
{
type
:
"
indexeddb
"
,
database
:
dbname
}
}
}
}
});
this
.
not_history
=
jIO
.
createJIO
({
type
:
"
query
"
,
sub_storage
:
{
type
:
"
uuid
"
,
sub_storage
:
{
type
:
"
indexeddb
"
,
database
:
dbname
}
}
});
this
.
blob
=
new
Blob
([
'
a
'
]);
}
});
test
(
"
Verifying pack works with keep_latest_num
"
,
function
()
{
stop
();
expect
(
2
);
var
jio
=
this
.
jio
,
not_history
=
this
.
not_history
;
return
jio
.
put
(
"
doc_a
"
,
{
title
:
"
rev
"
})
.
push
(
function
()
{
return
jio
.
put
(
"
doc_a
"
,
{
title
:
"
rev0
"
});
})
.
push
(
function
()
{
return
jio
.
put
(
"
doc_a
"
,
{
title
:
"
rev1
"
});
})
.
push
(
function
()
{
return
jio
.
put
(
"
doc_b
"
,
{
title
:
"
data
"
});
})
.
push
(
function
()
{
return
jio
.
put
(
"
doc_b
"
,
{
title
:
"
data0
"
});
})
.
push
(
function
()
{
return
jio
.
put
(
"
doc_a
"
,
{
title
:
"
rev2
"
});
})
.
push
(
function
()
{
return
jio
.
put
(
"
doc_b
"
,
{
title
:
"
data1
"
});
})
.
push
(
function
()
{
return
jio
.
put
(
"
doc_b
"
,
{
title
:
"
data2
"
});
})
.
push
(
function
()
{
return
jio
.
__storage
.
_sub_storage
.
__storage
.
_sub_storage
.
__storage
.
packOldRevisions
({
keep_latest_num
:
2
});
})
.
push
(
function
()
{
return
not_history
.
allDocs
({
sort_on
:
[[
"
timestamp
"
,
"
descending
"
]],
select_list
:
[
"
doc
"
,
"
doc_id
"
,
"
timestamp
"
,
"
op
"
]
});
})
.
push
(
function
(
results
)
{
equal
(
results
.
data
.
total_rows
,
4
,
"
Correct amount of results
"
);
deepEqual
(
results
.
data
.
rows
,
[
{
doc
:
{},
id
:
results
.
data
.
rows
[
0
].
id
,
value
:
{
doc
:
{
title
:
"
data2
"
},
doc_id
:
"
doc_b
"
,
timestamp
:
results
.
data
.
rows
[
0
].
id
,
op
:
"
put
"
}
},
{
doc
:
{},
id
:
results
.
data
.
rows
[
1
].
id
,
value
:
{
doc
:
{
title
:
"
data1
"
},
doc_id
:
"
doc_b
"
,
timestamp
:
results
.
data
.
rows
[
1
].
id
,
op
:
"
put
"
}
},
{
doc
:
{},
id
:
results
.
data
.
rows
[
2
].
id
,
value
:
{
doc
:
{
title
:
"
rev2
"
},
doc_id
:
"
doc_a
"
,
timestamp
:
results
.
data
.
rows
[
2
].
id
,
op
:
"
put
"
}
},
{
doc
:
{},
id
:
results
.
data
.
rows
[
3
].
id
,
value
:
{
doc
:
{
title
:
"
rev1
"
},
doc_id
:
"
doc_a
"
,
timestamp
:
results
.
data
.
rows
[
3
].
id
,
op
:
"
put
"
}
}
],
"
Keep the correct documents after pack
"
);
})
.
fail
(
function
(
error
)
{
//console.log(error);
ok
(
false
,
error
);
})
.
always
(
function
()
{
start
();
});
});
test
(
"
Verifying pack works with fixed timestamp
"
,
function
()
{
stop
();
expect
(
2
);
var
jio
=
this
.
jio
,
not_history
=
this
.
not_history
,
timestamp
;
return
jio
.
allDocs
()
.
push
(
function
()
{
return
RSVP
.
all
([
jio
.
put
(
"
doc_a
"
,
{
title
:
"
old_rev0
"
}),
jio
.
put
(
"
doc_a
"
,
{
title
:
"
old_rev1
"
}),
jio
.
put
(
"
doc_a
"
,
{
title
:
"
old_rev2
"
}),
jio
.
put
(
"
doc_b
"
,
{
title
:
"
old_data0
"
}),
jio
.
put
(
"
doc_b
"
,
{
title
:
"
old_data1
"
}),
jio
.
put
(
"
doc_b
"
,
{
title
:
"
old_data2
"
}),
jio
.
put
(
"
doc_c
"
,
{
title
:
"
latest_bar
"
})
]);
})
.
push
(
function
()
{
return
not_history
.
allDocs
({
sort_on
:
[[
"
timestamp
"
,
"
descending
"
]]});
})
.
push
(
function
(
results
)
{
timestamp
=
results
.
data
.
rows
[
0
].
id
;
return
jio
.
put
(
"
doc_a
"
,
{
title
:
"
latest_rev
"
});
})
.
push
(
function
()
{
return
jio
.
put
(
"
doc_b
"
,
{
title
:
"
latest_data
"
});
})
.
push
(
function
()
{
return
jio
.
__storage
.
_sub_storage
.
__storage
.
_sub_storage
.
__storage
.
packOldRevisions
({
keep_active_revs
:
timestamp
});
})
.
push
(
function
()
{
return
not_history
.
allDocs
({
sort_on
:
[[
"
timestamp
"
,
"
descending
"
]],
select_list
:
[
"
doc
"
,
"
doc_id
"
,
"
timestamp
"
]
});
})
.
push
(
function
(
results
)
{
equal
(
results
.
data
.
total_rows
,
3
,
"
Correct amount of results
"
);
deepEqual
(
results
.
data
.
rows
,
[
{
doc
:
{},
id
:
results
.
data
.
rows
[
0
].
id
,
value
:
{
doc
:
{
title
:
"
latest_data
"
},
doc_id
:
"
doc_b
"
,
timestamp
:
results
.
data
.
rows
[
0
].
id
}
},
{
doc
:
{},
id
:
results
.
data
.
rows
[
1
].
id
,
value
:
{
doc
:
{
title
:
"
latest_rev
"
},
doc_id
:
"
doc_a
"
,
timestamp
:
results
.
data
.
rows
[
1
].
id
}
},
{
doc
:
{},
id
:
results
.
data
.
rows
[
2
].
id
,
value
:
{
doc
:
{
title
:
"
latest_bar
"
},
doc_id
:
"
doc_c
"
,
timestamp
:
results
.
data
.
rows
[
2
].
id
}
}
],
"
Keep the correct documents after pack
"
);
})
.
fail
(
function
(
error
)
{
//console.log(error);
ok
(
false
,
error
);
})
.
always
(
function
()
{
start
();
});
});
test
(
"
Verifying pack works with fixed timestamp and more complex operations
"
,
function
()
{
stop
();
expect
(
2
);
var
jio
=
this
.
jio
,
not_history
=
this
.
not_history
,
timestamp
;
return
jio
.
allDocs
()
.
push
(
function
()
{
return
RSVP
.
all
([
jio
.
put
(
"
doc_a
"
,
{
title
:
"
old_rev0
"
}),
jio
.
put
(
"
doc_a
"
,
{
title
:
"
old_rev1
"
}),
jio
.
put
(
"
doc_a
"
,
{
title
:
"
old_rev2
"
}),
jio
.
put
(
"
doc_b
"
,
{
title
:
"
latest_data
"
})
]);
})
.
push
(
function
()
{
return
jio
.
allDocs
({
sort_on
:
[[
"
timestamp
"
,
"
descending
"
]]});
})
.
push
(
function
(
results
)
{
timestamp
=
results
.
data
.
rows
[
0
].
id
;
return
jio
.
remove
(
"
doc_a
"
);
})
.
push
(
function
()
{
return
jio
.
__storage
.
_sub_storage
.
__storage
.
_sub_storage
.
__storage
.
packOldRevisions
({
keep_active_revs
:
timestamp
});
})
.
push
(
function
()
{
return
not_history
.
allDocs
({
sort_on
:
[[
"
timestamp
"
,
"
descending
"
]],
select_list
:
[
"
doc
"
,
"
doc_id
"
,
"
timestamp
"
,
"
op
"
]
});
})
.
push
(
function
(
results
)
{
deepEqual
(
results
.
data
.
rows
,
[
{
doc
:
{},
id
:
results
.
data
.
rows
[
0
].
id
,
value
:
{
op
:
"
remove
"
,
doc_id
:
"
doc_a
"
,
timestamp
:
results
.
data
.
rows
[
0
].
id
}
},
{
doc
:
{},
id
:
results
.
data
.
rows
[
1
].
id
,
value
:
{
doc
:
{
title
:
"
latest_data
"
},
doc_id
:
"
doc_b
"
,
op
:
"
put
"
,
timestamp
:
results
.
data
.
rows
[
1
].
id
}
}
],
"
Keep the correct documents after pack
"
);
})
.
push
(
function
()
{
return
jio
.
allDocs
({
sort_on
:
[[
"
timestamp
"
,
"
descending
"
]],
select_list
:
[
"
title
"
]
});
})
.
push
(
function
(
results
)
{
deepEqual
(
results
.
data
.
rows
,
[
{
doc
:
{},
id
:
"
doc_b
"
,
value
:
{
title
:
"
latest_data
"
}
}
],
"
Memory not corrupted by pack without include_revisions
"
);
})
.
fail
(
function
(
error
)
{
//console.log(error);
ok
(
false
,
error
);
})
.
always
(
function
()
{
start
();
});
});
test
(
"
Verifying pack works with fixed timestamp and more complex operations
"
,
function
()
{
stop
();
expect
(
2
);
var
jio
=
this
.
jio
,
not_history
=
this
.
not_history
,
timestamp
;
return
jio
.
allDocs
()
.
push
(
function
()
{
return
RSVP
.
all
([
jio
.
put
(
"
doc_a
"
,
{
title
:
"
old_rev0
"
}),
jio
.
put
(
"
doc_a
"
,
{
title
:
"
old_rev1
"
}),
jio
.
put
(
"
doc_a
"
,
{
title
:
"
old_rev2
"
}),
jio
.
put
(
"
doc_b
"
,
{
title
:
"
latest_data
"
})
]);
})
.
push
(
function
()
{
return
jio
.
allDocs
({
sort_on
:
[[
"
timestamp
"
,
"
descending
"
]]});
})
.
push
(
function
(
results
)
{
timestamp
=
results
.
data
.
rows
[
0
].
id
;
return
jio
.
remove
(
"
doc_a
"
);
})
.
push
(
function
()
{
return
jio
.
__storage
.
_sub_storage
.
__storage
.
_sub_storage
.
__storage
.
packOldRevisions
({
keep_active_revs
:
timestamp
});
})
.
push
(
function
()
{
return
not_history
.
allDocs
({
sort_on
:
[[
"
timestamp
"
,
"
descending
"
]],
select_list
:
[
"
doc
"
,
"
doc_id
"
,
"
timestamp
"
,
"
op
"
]
});
})
.
push
(
function
(
results
)
{
deepEqual
(
results
.
data
.
rows
,
[
{
doc
:
{},
id
:
results
.
data
.
rows
[
0
].
id
,
value
:
{
op
:
"
remove
"
,
doc_id
:
"
doc_a
"
,
timestamp
:
results
.
data
.
rows
[
0
].
id
}
},
{
doc
:
{},
id
:
results
.
data
.
rows
[
1
].
id
,
value
:
{
doc
:
{
title
:
"
latest_data
"
},
doc_id
:
"
doc_b
"
,
op
:
"
put
"
,
timestamp
:
results
.
data
.
rows
[
1
].
id
}
}
],
"
Keep the correct documents after pack
"
);
})
.
push
(
function
()
{
return
jio
.
allDocs
({
sort_on
:
[[
"
timestamp
"
,
"
descending
"
]],
select_list
:
[
"
title
"
]
});
})
.
push
(
function
(
results
)
{
deepEqual
(
results
.
data
.
rows
,
[
{
doc
:
{},
id
:
"
doc_b
"
,
value
:
{
title
:
"
latest_data
"
}
}
],
"
Memory not corrupted by pack without include_revisions
"
);
})
.
fail
(
function
(
error
)
{
//console.log(error);
ok
(
false
,
error
);
})
.
always
(
function
()
{
start
();
});
});
test
(
"
Verifying pack works with fixed timestamp and more complex operations
"
,
function
()
{
stop
();
expect
(
2
);
var
jio
=
this
.
jio
,
not_history
=
this
.
not_history
,
timestamp
,
blob
=
this
.
blob
;
return
jio
.
allDocs
()
.
push
(
function
()
{
return
RSVP
.
all
([
jio
.
put
(
"
doc_a
"
,
{
title
:
"
old_rev0
"
}),
jio
.
putAttachment
(
"
doc_a
"
,
"
attach_aa
"
,
blob
),
jio
.
put
(
"
doc_b
"
,
{
title
:
"
latest_data
"
})
]);
})
.
push
(
function
()
{
return
jio
.
allDocs
({
sort_on
:
[[
"
timestamp
"
,
"
descending
"
]]});
})
.
push
(
function
(
results
)
{
timestamp
=
results
.
data
.
rows
[
0
].
id
;
return
jio
.
remove
(
"
doc_a
"
);
})
.
push
(
function
()
{
return
jio
.
__storage
.
_sub_storage
.
__storage
.
_sub_storage
.
__storage
.
packOldRevisions
({
keep_active_revs
:
timestamp
});
})
.
push
(
function
()
{
return
not_history
.
allDocs
({
sort_on
:
[[
"
timestamp
"
,
"
descending
"
]],
select_list
:
[
"
doc
"
,
"
doc_id
"
,
"
timestamp
"
,
"
op
"
]
});
})
.
push
(
function
(
results
)
{
deepEqual
(
results
.
data
.
rows
,
[
{
doc
:
{},
id
:
results
.
data
.
rows
[
0
].
id
,
value
:
{
op
:
"
remove
"
,
doc_id
:
"
doc_a
"
,
timestamp
:
results
.
data
.
rows
[
0
].
id
}
},
{
doc
:
{},
id
:
results
.
data
.
rows
[
1
].
id
,
value
:
{
doc
:
{
title
:
"
latest_data
"
},
doc_id
:
"
doc_b
"
,
op
:
"
put
"
,
timestamp
:
results
.
data
.
rows
[
1
].
id
}
}
],
"
Keep the correct documents after pack
"
);
})
.
push
(
function
()
{
return
jio
.
allDocs
({
sort_on
:
[[
"
timestamp
"
,
"
descending
"
]],
select_list
:
[
"
title
"
]
});
})
.
push
(
function
(
results
)
{
deepEqual
(
results
.
data
.
rows
,
[
{
doc
:
{},
id
:
"
doc_b
"
,
value
:
{
title
:
"
latest_data
"
}
}
],
"
Memory not corrupted by pack without include_revisions
"
);
})
.
fail
(
function
(
error
)
{
//console.log(error);
ok
(
false
,
error
);
})
.
always
(
function
()
{
start
();
});
});
}(
jIO
,
RSVP
,
Blob
,
QUnit
));
\ No newline at end of file
test/jio.storage/revisionstorage.tests.js
View file @
1f034e93
This source diff could not be displayed because it is too large. You can
view the blob
instead.
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment