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
Hide 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 */
/*global jIO, hex_sha256, define */
/**
* 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
)
{
/*jslint nomen: true*/
/*global RSVP, SimpleQuery, ComplexQuery*/
(
function
(
jIO
,
RSVP
,
SimpleQuery
,
ComplexQuery
)
{
"
use strict
"
;
var
tool
=
{
"
readBlobAsBinaryString
"
:
jIO
.
util
.
readBlobAsBinaryString
,
"
uniqueJSONStringify
"
:
jIO
.
util
.
uniqueJSONStringify
};
jIO
.
addStorage
(
"
revision
"
,
function
(
spec
)
{
var
that
=
this
,
priv
=
{};
spec
=
spec
||
{};
// ATTRIBUTES //
priv
.
doc_tree_suffix
=
"
.revision_tree.json
"
;
priv
.
sub_storage
=
spec
.
sub_storage
;
// METHODS //
/**
* Clones an object in deep (without functions)
* @method clone
* @param {any} object The object to clone
* @return {any} The cloned object
*/
priv
.
clone
=
function
(
object
)
{
var
tmp
=
JSON
.
stringify
(
object
);
if
(
tmp
===
undefined
)
{
return
undefined
;
}
return
JSON
.
parse
(
tmp
);
};
// Used to distinguish between operations done within the same millisecond
function
generateUniqueTimestamp
(
time
)
{
/**
* Generate a new uuid
* @method generateUuid
* @return {string} The new uuid
*/
priv
.
generateUuid
=
function
()
{
var
S4
=
function
()
{
/* 65536 */
var
i
,
string
=
Math
.
floor
(
Math
.
random
()
*
0x10000
).
toString
(
16
);
for
(
i
=
string
.
length
;
i
<
4
;
i
+=
1
)
{
string
=
'
0
'
+
string
;
}
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
);
};
/**
* Checks a revision format
* @method checkDocumentRevisionFormat
* @param {object} doc The document object
* @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
"
);
}
}
};
// 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
;
}
/**
* Creates a new document tree
* @method newDocTree
* @return {object} The new document tree
*/
priv
.
newDocTree
=
function
()
{
return
{
"
children
"
:
[]};
};
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
);
}
/**
* Convert revs_info to a simple revisions history
* @method revsInfoToHistory
* @param {array} revs_info The revs info
* @return {object} The revisions history
*/
priv
.
revsInfoToHistory
=
function
(
revs_info
)
{
var
i
,
revisions
=
{
"
start
"
:
0
,
"
ids
"
:
[]
};
revs_info
=
revs_info
||
[];
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
]);
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
};
}
return
revisions
;
};
log
=
seen
[
docum
.
value
.
doc_id
];
log
.
count
+=
1
;
//log.id = docum.id;
/**
* Convert the revision history object to an array of revisions.
* @method revisionHistoryToList
* @param {object} revs The revision history
* @return {array} The revision array
*/
priv
.
revisionHistoryToList
=
function
(
revs
)
{
var
i
,
start
=
revs
.
start
,
new_list
=
[];
for
(
i
=
0
;
i
<
revs
.
ids
.
length
;
i
+=
1
,
start
-=
1
)
{
new_list
.
push
(
start
+
"
-
"
+
revs
.
ids
[
i
]);
// 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
;
}
return
new_list
;
};
/**
* 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
"
});
// 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
;
}
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
;
};
/**
* 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
((
docum
.
op
===
"
putAttachment
"
||
docum
.
op
===
"
removeAttachment
"
)
&&
log
.
hasOwnProperty
(
docum
.
name
)
&&
!
log
[
docum
.
name
].
hasOwnProperty
(
"
prA
"
))
{
log
[
docum
.
name
].
prA
=
ind
;
log
.
final
=
ind
;
}
if
(
doc
.
_revs
.
start
>
0
)
{
doc
.
_rev
=
doc
.
_revs
.
start
+
"
-
"
+
doc
.
_revs
.
ids
[
0
];
}
else
{
delete
doc
.
_rev
;
}
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
);
}
/**
* Generates the next revision of a document.
* @methode generateNextRevision
* @param {object} doc The document metadata
* @param {boolean} deleted_flag The deleted flag
* @return {array} 0:The next revision number and 1:the hash code
*/
priv
.
generateNextRevision
=
function
(
doc
,
deleted_flag
)
{
var
string
,
revision_history
,
revs_info
;
doc
=
priv
.
clone
(
doc
)
||
{};
revision_history
=
doc
.
_revs
;
revs_info
=
doc
.
_revs_info
;
delete
doc
.
_rev
;
delete
doc
.
_revs
;
delete
doc
.
_revs_info
;
string
=
tool
.
uniqueJSONStringify
(
doc
)
+
tool
.
uniqueJSONStringify
(
revision_history
)
+
JSON
.
stringify
(
deleted_flag
?
true
:
false
);
revision_history
.
start
+=
1
;
revision_history
.
ids
.
unshift
(
priv
.
hashCode
(
string
));
doc
.
_revs
=
revision_history
;
doc
.
_rev
=
revision_history
.
start
+
"
-
"
+
revision_history
.
ids
[
0
];
revs_info
.
unshift
({
"
rev
"
:
doc
.
_rev
,
"
status
"
:
deleted_flag
?
"
deleted
"
:
"
available
"
});
doc
.
_revs_info
=
revs_info
;
return
doc
;
};
function
throwCantFindError
(
id
)
{
throw
new
jIO
.
util
.
jIOError
(
"
RevisionStorage: cannot find object '
"
+
id
+
"
'
"
,
404
);
}
/**
* 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
);
};
function
throwRemovedError
(
id
)
{
throw
new
jIO
.
util
.
jIOError
(
"
RevisionStorage: cannot find object '
"
+
id
+
"
' (removed)
"
,
404
);
}
priv
.
updateDocumentTree
=
function
(
doc
,
doc_tree
)
{
var
revs_info
,
updateDocumentTreeRec
;
doc
=
priv
.
clone
(
doc
);
revs_info
=
doc
.
_revs_info
;
updateDocumentTreeRec
=
function
(
doc_tree
,
revs_info
)
{
var
i
,
child
,
info
;
if
(
revs_info
.
length
===
0
)
{
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
);
}
}
doc_tree
.
children
.
unshift
({
"
rev
"
:
info
.
rev
,
"
status
"
:
info
.
status
,
"
children
"
:
[]
/**
* The jIO RevisionStorage extension
*
* @class RevisionStorage
* @constructor
*/
function
RevisionStorage
(
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
;
});
});
updateDocumentTreeRec
(
doc_tree
.
children
[
0
],
revs_info
);
};
updateDocumentTreeRec
(
doc_tree
,
priv
.
clone
(
revs_info
));
};
}
priv
.
send
=
function
(
command
,
method
,
doc
,
option
,
callback
)
{
var
storage
=
command
.
storage
(
priv
.
sub_storage
);
function
onSuccess
(
success
)
{
callback
(
undefined
,
success
);
}
function
onError
(
err
)
{
callback
(
err
,
undefined
);
}
if
(
method
===
'
allDocs
'
)
{
storage
.
allDocs
(
option
).
then
(
onSuccess
,
onError
);
}
else
{
storage
[
method
](
doc
,
option
).
then
(
onSuccess
,
onError
);
}
};
RevisionStorage
.
prototype
.
get
=
function
(
id_in
)
{
priv
.
getWinnerRevsInfo
=
function
(
doc_tree
)
{
var
revs_info
=
[],
getWinnerRevsInfoRec
;
getWinnerRevsInfoRec
=
function
(
doc_tree
,
tmp_revs_info
)
{
var
i
;
if
(
doc_tree
.
rev
)
{
tmp_revs_info
.
unshift
({
"
rev
"
:
doc_tree
.
rev
,
"
status
"
:
doc_tree
.
status
});
}
if
(
doc_tree
.
children
.
length
===
0
)
{
if
(
revs_info
.
length
===
0
||
(
revs_info
[
0
].
status
!==
"
available
"
&&
tmp_revs_info
[
0
].
status
===
"
available
"
)
||
(
tmp_revs_info
[
0
].
status
===
"
available
"
&&
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
,
[]);
return
revs_info
;
};
// Query to get the last edit made to this document
var
substorage
=
this
.
_sub_storage
,
doc_id_query
,
metadata_query
,
options
;
priv
.
getConflicts
=
function
(
revision
,
doc_tree
)
{
var
conflicts
=
[],
getConflictsRec
;
getConflictsRec
=
function
(
doc_tree
)
{
var
i
;
if
(
doc_tree
.
rev
===
revision
)
{
return
;
}
if
(
doc_tree
.
children
.
length
===
0
)
{
if
(
doc_tree
.
status
!==
"
deleted
"
)
{
conflicts
.
push
(
doc_tree
.
rev
);
}
}
for
(
i
=
0
;
i
<
doc_tree
.
children
.
length
;
i
+=
1
)
{
getConflictsRec
(
doc_tree
.
children
[
i
]);
}
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
);
});
};
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
"
};
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
);
};
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
);
};
priv
.
getDocument
=
function
(
command
,
doc
,
option
,
callback
)
{
doc
=
priv
.
clone
(
doc
);
doc
.
_id
=
doc
.
_id
+
"
.
"
+
doc
.
_rev
;
delete
doc
.
_attachment
;
delete
doc
.
_rev
;
delete
doc
.
_revs
;
delete
doc
.
_revs_info
;
priv
.
get
(
command
,
doc
,
option
,
callback
);
};
priv
.
putDocument
=
function
(
command
,
doc
,
option
,
callback
)
{
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
);
};
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
);
};
priv
.
getRevisionTree
=
function
(
command
,
doc
,
option
,
callback
)
{
doc
=
priv
.
clone
(
doc
);
doc
.
_id
=
doc
.
_id
+
priv
.
doc_tree_suffix
;
priv
.
get
(
command
,
doc
,
option
,
function
(
err
,
response
)
{
if
(
err
)
{
return
callback
(
err
,
response
);
}
if
(
response
.
data
&&
response
.
data
.
children
)
{
response
.
data
.
children
=
JSON
.
parse
(
response
.
data
.
children
);
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
{
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
callback
(
err
,
response
);
return
RSVP
.
hash
(
attachment_promises
);
});
};
};
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
);
RevisionStorage
.
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
;
}
}
result_list
.
push
({
"
_attachment
"
:
attachment_id
,
"
_data
"
:
response
.
data
,
"
_content_type
"
:
attachment_meta
.
content_type
});
if
(
count
===
0
)
{
state
=
"
finished
"
;
callback
(
undefined
,
{
"
data
"
:
result_list
});
}
};
}
return
this
.
_sub_storage
.
put
(
timestamp
,
metadata
)
.
push
(
function
()
{
return
substorage
.
putAttachment
(
timestamp
,
name
,
blob
);
});
};
RevisionStorage
.
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
"
]
};
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
"
:
[]});
}
};
priv
.
putAttachmentList
=
function
(
command
,
doc
,
option
,
attachment_list
,
callback
)
{
var
i
,
dealResults
,
state
=
"
ok
"
,
count
=
0
,
attachment
;
attachment_list
=
attachment_list
||
[];
dealResults
=
function
()
{
return
function
(
err
)
{
if
(
state
!==
"
ok
"
)
{
return
;
}
count
-=
1
;
if
(
err
)
{
state
=
"
error
"
;
return
callback
(
err
,
undefined
);
}
if
(
count
===
0
)
{
state
=
"
finished
"
;
callback
(
undefined
,
{});
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
);
}
};
};
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
));
return
substorage
.
getAttachment
(
results
.
data
.
rows
[
0
].
id
,
name
);
}
}
if
(
count
===
0
)
{
return
callback
(
undefined
,
{});
}
};
priv
.
putDocumentTree
=
function
(
command
,
doc
,
option
,
doc_tree
,
callback
)
{
doc_tree
=
priv
.
clone
(
doc_tree
);
doc_tree
.
_id
=
doc
.
_id
+
priv
.
doc_tree_suffix
;
if
(
doc_tree
.
children
)
{
doc_tree
.
children
=
JSON
.
stringify
(
doc_tree
.
children
);
}
priv
.
put
(
command
,
doc_tree
,
option
,
callback
);
};
throwCantFindError
(
id
);
});
};
priv
.
notFoundError
=
function
(
message
,
reason
)
{
return
{
"
status
"
:
404
,
"
message
"
:
message
,
"
reason
"
:
reason
RevisionStorage
.
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
);
};
priv
.
conflictError
=
function
(
message
,
reason
)
{
return
{
"
status
"
:
409
,
"
message
"
:
message
,
"
reason
"
:
reason
};
};
RevisionStorage
.
prototype
.
repair
=
function
()
{
return
this
.
_sub_storage
.
repair
.
apply
(
this
.
_sub_storage
,
arguments
);
};
priv
.
revisionGenericRequest
=
function
(
command
,
doc
,
option
,
specific_parameter
,
onEnd
)
{
var
prev_doc
,
doc_tree
,
attachment_list
,
callback
=
{};
if
(
specific_parameter
.
doc_id
)
{
doc
.
_id
=
specific_parameter
.
doc_id
;
}
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
);
RevisionStorage
.
prototype
.
hasCapacity
=
function
(
name
)
{
return
name
===
'
list
'
||
name
===
'
include
'
||
name
===
'
query
'
||
name
===
'
select
'
;
};
RevisionStorage
.
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
;
}
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
(
docum
.
value
.
op
===
"
remove
"
)
{
docum
.
value
.
doc
=
{};
return
docum
;
}
}
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
(
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
;
}
}
}
}
}
}
if
(
specific_parameter
.
remove
)
{
priv
.
putDocumentTree
(
command
,
doc
,
option
,
doc_tree
,
callback
.
putDocumentTree
);
return
false
;
});
}
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
();
};
/**
* Post the document metadata and create or update a document tree.
* Options:
* - {boolean} keep_revision_history To keep the previous revisions
* (false by default) (NYI).
* @method post
* @param {object} command The JIO command
*/
that
.
post
=
function
(
command
,
metadata
,
option
)
{
priv
.
revisionGenericRequest
(
command
,
metadata
,
option
,
{},
function
(
err
,
response
)
{
if
(
err
)
{
return
command
.
error
(
err
);
}
command
.
success
({
"
id
"
:
response
.
id
,
"
rev
"
:
response
.
rev
});
}
);
};
/**
* Put the document metadata and create or update a document tree.
* 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
});
}
);
};
// 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
;
}
that
.
putAttachment
=
function
(
command
,
param
,
option
)
{
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
);
}
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
=
{};
}
}
}
}
command
.
success
({
"
rev
"
:
response
.
rev
});
}
);
},
function
()
{
command
.
error
(
"
conflict
"
,
"
broken blob
"
,
"
Cannot read data to put
"
);
});
};
that
.
remove
=
function
(
command
,
param
,
option
)
{
priv
.
revisionGenericRequest
(
command
,
param
,
option
,
{
"
revision_needed
"
:
true
,
"
remove
"
:
true
},
function
(
err
,
response
)
{
if
(
err
)
{
return
command
.
error
(
err
);
}
command
.
success
({
"
rev
"
:
response
.
rev
});
return
false
;
});
}
);
};
docs_to_query
=
results
that
.
removeAttachment
=
function
(
command
,
param
,
option
)
{
priv
.
revisionGenericRequest
(
command
,
param
,
option
,
{
"
doc_id
"
:
param
.
_id
,
"
attachment_id
"
:
param
.
_attachment
,
"
revision_needed
"
:
true
,
"
removeAttachment
"
:
true
,
"
remove_from_attachment_list
"
:
{
"
_attachment
"
:
param
.
_attachment
}
},
function
(
err
,
response
)
{
if
(
err
)
{
return
command
.
error
(
err
);
}
command
.
success
({
"
rev
"
:
response
.
rev
});
}
);
};
// Filter out all docs flagged as false in previous map call
.
filter
(
function
(
docum
)
{
return
docum
;
})
that
.
get
=
function
(
command
,
param
,
option
)
{
priv
.
revisionGenericRequest
(
command
,
param
,
option
,
{
"
get
"
:
true
},
function
(
err
,
response
)
{
if
(
err
)
{
return
command
.
error
(
err
);
}
command
.
success
({
"
data
"
:
response
.
data
});
}
);
};
// Put into correct format to be passed back to query storage
.
map
(
function
(
docum
)
{
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
});
}
);
};
that
.
allDocs
=
function
(
command
,
param
,
option
)
{
/*jslint unparam: true */
var
rows
,
result
=
{
"
total_rows
"
:
0
,
"
rows
"
:
[]},
functions
=
{};
functions
.
finished
=
0
;
functions
.
falseResponseGenerator
=
function
(
response
,
callback
)
{
callback
(
undefined
,
response
);
};
functions
.
fillResultGenerator
=
function
(
doc_id
)
{
return
function
(
err
,
doc_tree
)
{
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
;
}
functions
.
success
();
};
};
functions
.
success
=
function
()
{
functions
.
finished
-=
1
;
if
(
functions
.
finished
===
0
)
{
command
.
success
({
"
data
"
:
result
});
}
};
priv
.
send
(
command
,
"
allDocs
"
,
null
,
option
,
function
(
err
,
response
)
{
var
i
,
row
,
selector
,
selected
;
if
(
err
)
{
return
command
.
error
(
err
);
}
response
=
response
.
data
;
selector
=
/
\.
revision_tree
\.
json$/
;
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
{
// 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
;
}
}
}
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
)
);
if
(
include_revs
)
{
docum
.
id
=
docum
.
value
.
timestamp
;
//docum.doc = docum.value.doc;
}
else
{
priv
.
getRevisionTree
(
command
,
{
"
_id
"
:
rows
.
revision_trees
[
i
].
id
},
option
,
functions
.
fillResultGenerator
(
rows
.
revision_trees
[
i
].
id
)
);
docum
.
id
=
docum
.
value
.
doc_id
;
//docum.doc = docum.value.doc;
}
}
}
functions
.
success
();
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
;
});
};
// XXX
that
.
check
=
function
(
command
)
{
command
.
success
();
};
// XXX
that
.
repair
=
function
(
command
)
{
command
.
success
();
};
};
});
// 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