Skip to content

Instantly share code, notes, and snippets.

@brucemcpherson
Last active December 25, 2015 07:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save brucemcpherson/6937450 to your computer and use it in GitHub Desktop.
Save brucemcpherson/6937450 to your computer and use it in GitHub Desktop.
'gistThat@mcpher.com :do not modify this line - see ramblings.mcpher.com for details: updated on 8/9/2014 3:09:54 PM : from manifest:5055578 gist https://gist.github.com/brucemcpherson/6937450/raw/cOauth2.cls
Option Explicit
'" based on Kyle Beachill Oauth2
'" for credits and details see
'" http://ramblings.mcpher.com/Home/excelquirks/guests/oauth2
' for details on this implementation see http://ramblings.mcpher.com/Home/excelquirks/googleoauth2
' v1.5
Private pPackage As cJobject
Private pCb As cBrowser
Private pDialog As cJobject
Private pSalt As String
Private pMessage As String
Public Function googleAuth(scopeEntry As String, _
Optional replacementConsole As cJobject = Nothing, _
Optional clientID As String = vbNullString, _
Optional clientSecret As String = vbNullString, _
Optional complain As Boolean = True, _
Optional cloneFromScopeEntry As String = vbNullString) As cOauth2
' this will do an oauth2 against google - the generated token() is usuable for subsequent requests against the given scope
' a auth interactive dialog wont be necessary if there is a refresh token, or an unexpired auth token
' available in the registry for this user.
' All data in registry is encrypted, and package from the last access is stored there
' this mimics the behavior of sites that authorized against google credentials
' setting up for the fist time.
' the first time this is used, there is nothing in the registry, so this needs to be called with either
' a cJobject which is simply the JSONPARSE of the string you can download from google when you register an app
' or, the clientID and client secret
' or you can clone all this from another scope
' once this has ever been called, the registry is used for the source of all this.
' refreshing the registry
' if for any reason the registry is stale, just repeat as if setting up for the first time
Dim authFlavor As String, aKey As String, sKey As String, clonePackage As cJobject
authFlavor = "google"
aKey = "type_" & authFlavor
sKey = "scope_" & scopeEntry
Set googleAuth = Me
' first see if we are cloning from another scope
If (cloneFromScopeEntry <> vbNullString) Then
Set clonePackage = getRegistryPackage(aKey, "scope_" & cloneFromScopeEntry)
End If
If (clientID <> vbNullString Or clientSecret <> vbNullString Or Not replacementConsole Is Nothing _
Or Not clonePackage Is Nothing) Then
' need a refresh
If (clonePackage Is Nothing) Then
Set pPackage = googlePackage(replacementConsole)
Else
' use the cloned credentials, but revoke it to force a new authentication
Set pPackage = clonePackage
revoke
End If
' use replacement package and add any passwords passed over (they may already be in the package)
With pPackage.child("parameters")
If clientSecret <> vbNullString Then .add "client_secret", clientSecret
If clientID <> vbNullString Then .add "client_id", clientID
.add "authFlavor", aKey
.add "scopeEntry", sKey
End With
Else
' get existing ppackage from registry if there is one
Set pPackage = getRegistryPackage(aKey, sKey)
If (pPackage Is Nothing) Then
pMessage = "need to supply new credentials - nothing in registry"
Exit Function
End If
End If
' this looks up the URL for the given scope entry
addGoogleScope (scopeEntry)
' do the auth
If (Not hasRefreshToken) Then
' " first step is to either get user consent and get a new token
getUserConsent
' " now get an access token
Set googleAuth = getToken
ElseIf (isExpired) Then
' " or to refresh an old one
Set googleAuth = getToken("refresh_token")
Else
' " or we are already good
Set googleAuth = Me
End If
' " update registry package
setRegistryPackage
End Function
'" do we have a token
Public Property Get hasToken() As Boolean
hasToken = token <> ""
End Property
' the auth header
Public Property Get authHeader() As String
If hasToken Then authHeader = tokenType & " " & token
End Property
'" the token
Public Property Get token() As String
token = getItemValue("parameters.access_token")
End Property
'" the denied error
Public Property Get denied() As String
denied = getItemValue("parameters.deniedCode") & pMessage
End Property
Private Function revoke() As cOauth2
With pPackage.child("parameters")
.add "access_token", vbNullString
.add "refresh_token", vbNullString
.add "code", vbNullString
End With
Set revoke = Me
End Function
'" the first phase - get user consent to proceed
Private Function getUserConsent() As cOauth2
If pCb Is Nothing Then
Set pCb = New cBrowser
End If
Dim phase As String
phase = "userConsent"
With pCb
.init().Navigate _
createUrl(phase) & "?" & generatePhaseParameters(phase), True
' " store results
With pPackage.child("parameters")
.add "code", pCb.successCode
.add "deniedCode", pCb.deniedCode
End With
End With
Set getUserConsent = Me
End Function
'" if we got user consent, then we can go ahead and get an access token
Private Function getToken(Optional phase As String = "authorization_code") As cOauth2
Dim job As cJobject, joc As cJobject
If isAuthenticated Then
If pCb Is Nothing Then
Set pCb = New cBrowser
End If
pPackage.child("parameters").add "grant_type", phase
pCb.httpPost createUrl(phase), generatePhaseParameters(phase)
With pPackage.child("parameters")
If (pCb.status <> 200) Then
revoke
.add "deniedCode", pCb.status
Else
Set job = JSONParse(pCb.Text)
For Each joc In job.children
.add joc.key, joc.value
Next joc
If hasRefreshToken Then
.add "expires", addSeconds(Now(), expiresIn)
End If
End If
End With
End If
Set getToken = Me
End Function
Private Function addSeconds(d As Date, n As Long) As Date
addSeconds = DateAdd("s", n, d)
End Function
'" - none of these need to be exposed --
'" do we have user consent
Private Property Get isAuthenticated() As Boolean
isAuthenticated = code <> ""
End Property
Private Property Get tokenType() As String
tokenType = getItemValue("parameters.token_type")
End Property
Private Property Get expiresIn() As Long
expiresIn = getItemValue("parameters.expires_in")
End Property
Private Property Get expires() As Date
expires = getItemValue("parameters.expires")
End Property
'" the user consent
Private Property Get code() As String
code = getItemValue("parameters.code")
End Property
Private Property Get hasRefreshToken() As Boolean
hasRefreshToken = refreshToken <> vbNullString
End Property
Private Property Get isExpired() As Boolean
isExpired = addSeconds(Now(), 660) > expires
End Property
'" the refresh token
Private Property Get refreshToken() As String
refreshToken = getItemValue("parameters.refresh_token")
End Property
Private Function getItemValue(key As String) As String
On Error GoTo crapped
If Not pPackage Is Nothing Then
getItemValue = pPackage.child(key).toString
Else
getItemValue = vbNullString
End If
Exit Function
crapped:
getItemValue = vbNullString
End Function
Private Function createUrl(parameterType As String) As String
createUrl = getItemValue("parameters." & pDialog.child(parameterType).toString("url"))
End Function
Private Function generatePhaseParameters(whichPhase As String) As String
Dim joc As cJobject, s As cStringChunker
' " this will construct a parameterstring for everything needed for an authentication phase
Set s = New cStringChunker
With pDialog.child(whichPhase).child("parameters")
For Each joc In .children
With pPackage.child("parameters").child(joc.value)
If (.toString <> vbNullString) Then
s.add(.key).add("=").add(.toString).add ("&")
End If
End With
Next joc
End With
generatePhaseParameters = s.chopIf("&").content
Set s = Nothing
End Function
'" release up memory - should be called when done
Public Function teardown() As cOauth2
If (Not pPackage Is Nothing) Then
pPackage.teardown
Set pPackage = Nothing
End If
If (Not pDialog Is Nothing) Then
pDialog.teardown
Set pDialog = Nothing
End If
If (Not pCb Is Nothing) Then
pCb.teardown
Set pCb = Nothing
End If
Set teardown = Me
End Function
'"---- encryption ----
Public Property Let salt(p As String)
' " you can change the encrypt salt if necessary
pSalt = p
End Property
Private Function encrypt(s As String) As String
' " uses capicom
encrypt = encryptMessage(s, pSalt)
End Function
Private Function decrypt(s As String) As String
' " uses capicom
decrypt = decryptMessage(s, pSalt)
End Function
'" ---- registry -----
'" in registry entries, the values are encrypted useing the salt
'" the structure is
'" xLiberation/googleAuth/scope - json pPackage values
Private Function getRegistryPackage(authFlavor As String, scopeEntry As String) As cJobject
Dim s As String
s = GetSetting("xLiberation", _
authFlavor, _
scopeEntry)
If (s <> vbNullString) Then Set getRegistryPackage = JSONParse(decrypt(s))
End Function
Private Function setRegistryPackage() As cJobject
Dim s As String
s = JSONStringify(pPackage)
SaveSetting "xLiberation", _
getItemValue("parameters.authFlavor"), _
getItemValue("parameters.scopeEntry"), _
encrypt(s)
End Function
Private Function describeDialog() As cJobject
Set describeDialog = JSONParse( _
"{'userConsent':" & _
"{'url':'url','parameters':" & _
"['response_type','token_uri','redirect_uri','client_id','scope']}," & _
"'authorization_code':{'url':'token_uri','parameters':['code','client_id'," & _
"'client_secret','redirect_uri','grant_type']}," & _
"'refresh_token':{'url':'refresh_uri','parameters'" & _
":['refresh_token','client_id','client_secret','grant_type']}}")
End Function
'" this is the package describing google oauth2
Private Function googlePackage(Optional consolePackage As cJobject = Nothing) As cJobject
Dim job As cJobject, package As cJobject, p As cJobject, c As cJobject
' use data provided or create an empty one
If consolePackage Is Nothing Then
Set job = makeBasicGoogleConsole()
Else
Set job = consolePackage
End If
Set package = skeletonPackage
Set c = job.child("installed")
Set p = package.child("parameters")
addFromOther c, p, "url", "auth_uri"
addFromOther c, p, "token_uri"
p.add "response_type", "code"
p.add "redirect_uri", c.child("redirect_uris.1").value
addFromOther c, p, "client_id"
addFromOther c, p, "client_secret"
addFromOther c, p, "refresh_uri", "token_uri"
p.add "grant_type", "authorization_code"
Set googlePackage = package
If (consolePackage Is Nothing) Then job.teardown
End Function
Private Sub addFromOther(c As cJobject, p As cJobject, k As String, Optional ok As String = vbNullString)
If ok = vbNullString Then ok = k
With p
If Not c.childExists(ok) Is Nothing Then
.add k, c.child(ok).value
Else
.add k
End If
End With
End Sub
Public Function addGoogleScope(s As String) As cOauth2
With pPackage.child("parameters")
Select Case s
Case "analytics"
.add "scope", URLEncode("https://www.googleapis.com/auth/analytics.readonly")
Case "drive"
.add "scope", URLEncode("https://www.googleapis.com/auth/drive")
Case "feeds"
.add "scope", URLEncode("https://spreadsheets.google.com/feeds")
Case "viz"
.add "scope", URLEncode("https://spreadsheets.google.com/feeds") + "+" & _
URLEncode("https://www.googleapis.com/auth/drive")
Case "urlshortener"
.add "scope", URLEncode("https://www.googleapis.com/auth/urlshortener")
Case default
Debug.Assert False
End Select
End With
Set addGoogleScope = Me
End Function
Private Function makeBasicGoogleConsole() As cJobject
Dim consoleJSON As String
' " when you register your app with google, you can download the Json
consoleJSON = _
"{'installed':{'auth_uri':'https://accounts.google.com/o/oauth2/auth'," & _
"'token_uri':'https://accounts.google.com/o/oauth2/token'," & _
"'redirect_uris':['urn:ietf:wg:oauth:2.0:oob','oob']," & _
"'auth_provider_x509_cert_url':'https://www.googleapis.com/oauth2/v1/certs'}}"
' " then parse it
Set makeBasicGoogleConsole = JSONParse(consoleJSON)
End Function
Public Function skeletonPackage() As cJobject
Dim package As cJobject
Set package = New cJobject
With package.init(Nothing)
With .add("parameters")
.add "url"
.add "token_uri"
.add "response_type"
.add "redirect_uri"
.add "client_id"
.add "code"
.add "client_secret"
.add "refresh_uri"
.add "grant_type"
.add "scope"
.add "authFlavor"
.add "scopeEntry"
.add "deniedCode"
End With
End With
Set skeletonPackage = package
End Function
Private Sub Class_Initialize()
Set pDialog = describeDialog
pSalt = "xLiberation"
End Sub
'gistThat@mcpher.com :do not modify this line - see ramblings.mcpher.com for details: updated on 8/9/2014 3:09:55 PM : from manifest:5055578 gist https://gist.github.com/brucemcpherson/6937174/raw/oauthExamples.vba
Option Explicit
' oauth examples
' v1.2
' convienience function for auth..
Public Function getGoogled(scope As String, _
Optional replacementpackage As cJobject = Nothing, _
Optional clientID As String = vbNullString, _
Optional clientSecret As String = vbNullString, _
Optional complain As Boolean = True, _
Optional cloneFromeScope As String = vbNullString) As cOauth2
Dim o2 As cOauth2
Set o2 = New cOauth2
With o2.googleAuth(scope, replacementpackage, clientID, clientSecret, complain, cloneFromeScope)
If Not .hasToken And complain Then
MsgBox ("Failed to authorize to google for scope " & scope & ":denied code " & o2.denied)
End If
End With
Set getGoogled = o2
End Function
Private Sub testOauth2()
Dim myConsole As cJobject
' if you are calling for the first time ever you can either provide your
' clientid/clientsecret - or pass the the jsonparse retrieved from the google app console
' normally all this stuff comes from encrpted registry store
' first ever
'Set myConsole = makeMyGoogleConsole
'With getGoogled("analytics", myConsole)
' Debug.Print .authHeader
' .tearDown
' End With
'or you can do first ever like this
'With getGoogled("viz", , "xxxxx.apps.googleusercontent.com", "xxxxx")
' Debug.Print .authHeader
' .tearDown
'End With
' all other times this is what is needed
With getGoogled("drive")
Debug.Print .authHeader
.teardown
End With
' lets auth and have a look at the contents
'Debug.Print objectStringify(getGoogled("drive"))
' all other times this is what is needed
With getGoogled("analytics")
Debug.Print .authHeader
.teardown
End With
' here's an example of cloning credentials from a different scope for 1st time in
With getGoogled("urlshortener", , , , , "drive")
Debug.Print .authHeader
.teardown
End With
With getGoogled("urlshortener")
Debug.Print .authHeader
.teardown
End With
' if you made one, clean it up
If Not myConsole Is Nothing Then
myConsole.teardown
End If
End Sub
Private Function makeMyGoogleConsole() As cJobject
Dim consoleJSON As String
consoleJSON = _
"{'installed':{'auth_uri':'https://accounts.google.com/o/oauth2/auth'," & _
"'client_secret':'xxxxxxxx'," & _
"'token_uri':'https://accounts.google.com/o/oauth2/token'," & _
"'client_email':'','redirect_uris':['urn:ietf:wg:oauth:2.0:oob','oob']," & _
"'client_x509_cert_url':'','client_id':'xxxxxxx.apps.googleusercontent.com'," & _
"'auth_provider_x509_cert_url':'https://www.googleapis.com/oauth2/v1/certs'}}"
Set makeMyGoogleConsole = JSONParse(consoleJSON)
End Function
Private Sub showLinkedinConsole()
Dim url As String, cb As cBrowser
Set cb = New cBrowser
' see http://excelramblings.blogspot.co.uk/2012/10/somewhere-to-keep-those-api-keys-google.html
' for how to store credentials in a google lockbox
url = "https://script.google.com/a/macros/mcpher.com/s/" & _
"AKfycbza96-Mpa47jlqXoPosk64bUfR8T7zO5POZMYyN45InrvX8gm28/exec" & _
"?action=show&entry=linkedinauth"
With getGoogled("drive")
If .hasToken Then
Debug.Print cb.httpGET(url, , , , , .authHeader)
Else
MsgBox ("failed to authenticate: " & .denied)
End If
.teardown
End With
cb.teardown
End Sub
' db abstraction tests
Private Function every(a As Variant) As Boolean
Dim i As Long, good As Boolean
If (IsArray(a)) Then
For i = LBound(a) To UBound(a)
If (Not a(i)) Then
good = False
Exit For
End If
good = True
Next i
Else
good = a
End If
every = good
End Function
Private Function assert(what As Variant, message As cJobject, n As String) As Boolean
Dim fatal As Boolean, m As String, good As Boolean
fatal = True
good = every(what)
m = "assertion:" & n
If (Not good) Then
m = m & ":failed:" & message.stringify
If fatal Then
Debug.Print m
Debug.Assert good
End If
End If
assert = good
Debug.Print m
End Function
Private Function setUpTest(dbtype As String, siloid As String, dbid As String) As cDbAb
' get authorized - using drive scope
Dim oauth2 As cOauth2, handler As cDbAb
Dim ds As cDataSet, testData As cJobject
Set oauth2 = getGoogled("drive")
' set up handler & set end point
Set oauth2 = getGoogled("drive")
Set handler = New cDbAb
With handler.setOauth2(oauth2)
.setEndPoint ("https://script.google.com/macros/s/AKfycbyfapkJpd4UOhiqLOJOGBb11nG4BTru_Bw8bZQ49eQSTfL2vbU/exec")
.setDbId ("13ccFPXI0L8-ZViHlv8qoVspotUcnX8v0ZFeY4nUP574")
.setNoCache (1)
.setDbId (dbid)
.setSiloId (siloid)
.setDbName dbtype
End With
Set setUpTest = handler
End Function
Public Function testDbAb()
' get authorized - using drive scope
Dim handler As cDbAb
Dim ds As cDataSet, testData As cJobject, result As cDbAbResult, testSheet As String
'get testdata from a google sheet & write it to an excel sheet
testSheet = "dbab"
Set handler = setUpTest("sheet", "customers", "13ccFPXI0L8-ZViHlv8qoVspotUcnX8v0ZFeY4nUP574")
Set result = handler.query()
assert result.handleCode >= 0, result.response, "getting testData from google sheet"
Set ds = makeSheetFromJob(result.data, testSheet)
Set testData = ds.jObject(, , , , "data")
' set up handler & set end point
Set handler = setUpTest("datastore", ds.name, "xliberationdatastore")
lotsoftests handler, testData
handler.teardown
Set handler = setUpTest("mongolab", ds.name, "xliberation")
lotsoftests handler, testData
handler.teardown
Set handler = setUpTest("parse", ds.name, "xliberation")
lotsoftests handler, testData
handler.teardown
Set handler = setUpTest("drive", ds.name, "/datahandler/driverdrive")
lotsoftests handler, testData
handler.teardown
Set handler = setUpTest("sheet", ds.name, "13ccFPXI0L8-ZViHlv8qoVspotUcnX8v0ZFeY4nUP574")
lotsoftests handler, testData
handler.teardown
ds.teardown
End Function
Private Sub lotsoftests(handler As cDbAb, testData As cJobject)
' remove from last time
Dim result As cDbAbResult, r2 As cDbAbResult
Dim x As Long, job As cJobject
Set result = handler.remove()
Debug.Print "Starting " & handler.getDbName
assert result.handleCode >= 0, result.response, "removing initial"
' save the new data
Set result = handler.save(testData)
assert result.handleCode >= 0, result.response, "saving initial"
' query and make sure it matches what was saved
Set result = handler.query()
assert Array(result.handleCode >= 0, result.count = testData.children.count), _
result.response, "querying initial"
'--------query everything with limit
Set result = handler.query(, "{'limit':2}")
assert Array(result.handleCode >= 0, result.count = 2), _
result.response, "limit test(" & result.count & ")"
'------Sort Reverse
Set result = handler.query(, "{'sort':'-name'}")
assert Array(result.handleCode >= 0, result.count = testData.children.count), _
result.response, "querysort(" & result.count & ")"
'------Sort Reverse/skip
Set result = handler.query(, "{'sort':'-name','skip':3}")
assert Array(result.handleCode >= 0, result.count = testData.children.count - 3), _
result.response, "querysort+skip(" & result.count & ")"
'------query simple nosql
Set result = handler.query("{'name':'ethel'}")
x = 0
For Each job In testData.children
x = x + -1 * CLng(job.child("name").value = "ethel")
Next job
assert Array(result.handleCode >= 0, result.count = x), _
result.response, "filterdot0(" & result.count & ")"
'------query multi level
Set result = handler.query("{'stuff':{'sex':'female'}}")
x = 0
For Each job In testData.children
x = x + -1 * CLng(job.child("stuff.sex").value = "female")
Next job
assert Array(result.handleCode >= 0, result.count = x), _
result.response, "filter(" & result.count & ")"
'------queries in
Set result = handler.query("{'name':" & _
handler.constraints("[['IN',['ethel','fred']]]") & "}", , True)
x = 0
For Each job In testData.children
x = x + -1 * (job.toString("name") = "ethel" Or job.toString("name") = "fred")
Next job
assert Array(result.handleCode >= 0, result.count = x), _
result.response, "filterdotc4 (" & result.count & ")"
'------first complex constraints
Set result = handler.query("{'stuff.age':" & _
handler.constraints("[['GT',25],['LTE',60]]") & "}")
' checking results kind of long winded in vba
x = 0
For Each job In testData.children
x = x + -1 * CLng(job.child("stuff.age").value > 25 And job.child("stuff.age").value <= 60)
Next job
assert Array(result.handleCode >= 0, result.count = x), _
result.response, "querying initial complex(" & result.count & ")"
'------query single constraint
Set result = handler.query("{'stuff':{'age':" & _
handler.constraints("[['GT',25]]") & "}}")
x = 0
For Each job In testData.children
x = x + -1 * CDbl(job.child("stuff.age").value > 25)
Next job
assert Array(result.handleCode >= 0, result.count = x), _
result.response, "filterdotc1 (" & result.count & ")"
'------2 queries same constraint
Set result = handler.query("{'stuff':{'age':" & _
handler.constraints("[['GT',25],['LT',60]]") & "}}")
x = 0
For Each job In testData.children
x = x + -1 * CDbl(job.child("stuff.age").value > 25 And job.child("stuff.age").value < 60)
Next job
assert Array(result.handleCode >= 0, result.count = x), _
result.response, "filterdotc2 (" & result.count & ")"
'------2 queries same constraint
Set result = handler.query("{'stuff':{'sex':'male', 'age':" & _
handler.constraints("[['GTE',25],['LT',60]]") & "}}", , True)
x = 0
For Each job In testData.children
x = x + -1 * (job.child("stuff.age").value >= 25 And job.child("stuff.age").value < 60 _
And job.child("stuff.sex").value = "male")
Next job
assert Array(result.handleCode >= 0, result.count = x), _
result.response, "filterdotc3 (" & result.count & ")"
'------queries in +
Set result = handler.query( _
"{'name':" & handler.constraints("[['IN',['john','mary']]]") & _
",'stuff.sex':'male','stuff.age':" & handler.constraints("[['GT',25]]") & "}")
x = 0
For Each job In testData.children
x = x + -1 * (job.child("stuff.sex").value = "male" And job.child("stuff.age").value > 25 And _
(job.toString("name") = "john" Or job.toString("name") = "mary"))
Next job
assert Array(result.handleCode >= 0, result.count = x), _
result.response, "filterdotc5 (" & result.count & "/" & x & ")"
'------query single constraint, get keys
Set result = handler.query( _
"{'stuff.age':" & handler.constraints("[['GT',25]]") & "}", _
"{'limit':1}", , True)
x = 1
assert Array(result.handleCode >= 0, result.handleKeys.children.count = 1), _
result.response, "limitkeycheck1 (" & result.count & ")"
'-------testing Get -- known as getobjects because get is reserved in vba
Set r2 = handler.getObjects(result.handleKeys)
x = 0
For Each job In r2.data.children
x = x + -1 * CDbl(job.child("stuff.age").value > 25)
Next job
assert Array(r2.handleCode >= 0, r2.count = 1, x = r2.count), _
result.response, "get1 (" & r2.count & ")"
'------retest constraint
Set result = handler.query("{'stuff':{'age':" & _
handler.constraints("[['GT',60]]") & "}}")
x = 0
For Each job In testData.children
x = x + -1 * CDbl(job.child("stuff.age").value > 60)
Next job
assert Array(result.handleCode >= 0, result.count = x), _
result.response, "repeat test easy (" & result.count & ")"
'------get ready for update test
Set result = handler.query("{'stuff.sex':'male'}", , 1, 1)
x = 0
For Each job In testData.children
x = x + -1 * CDbl(job.child("stuff.sex").value = "male")
Next job
assert Array(result.handleCode >= 0, result.handleKeys.children.count = x), _
result.response, "does male work(" & result.count & ")"
'----- do the update
'first update the data with a new field
For Each job In result.data.children
job.add "stuff.man", job.child("stuff.sex").value = "male"
Next job
' now update it
Set r2 = handler.update(result.handleKeys, result.data)
assert Array(r2.handleCode = 0), _
r2.response, "update 2 (" & r2.count & ")"
'------check previous query still works
Set result = handler.query( _
"{'name':" & handler.constraints("[['IN',['john','mary']]]") & _
",'stuff.sex':'male','stuff.age':" & handler.constraints("[['GT',25]]") & "}")
x = 0
For Each job In testData.children
x = x + -1 * (job.child("stuff.sex").value = "male" And job.child("stuff.age").value > 25 And _
(job.toString("name") = "john" Or job.toString("name") = "mary"))
Next job
assert Array(result.handleCode >= 0, result.count = x), _
result.response, "repeat test after update (" & result.count & "/" & x & ")"
' query again and make sure it matches what was saved
Set result = handler.query()
assert Array(result.handleCode >= 0, result.count = testData.children.count), _
result.response, "repeat querying initial"
' try counting
Set result = handler.count()
assert Array(result.handleCode >= 0, result.count = testData.children.count), _
result.response, "count 1"
' try complicated counting
Set result = handler.count( _
"{'name':" & handler.constraints("[['IN',['john','mary']]]") & _
",'stuff.sex':'male','stuff.age':" & handler.constraints("[['GT',25]]") & "}")
x = 0
For Each job In testData.children
x = x + -1 * (job.child("stuff.sex").value = "male" And job.child("stuff.age").value > 25 And _
(job.toString("name") = "john" Or job.toString("name") = "mary"))
Next job
assert Array(result.handleCode >= 0, result.count = x), _
result.response, "complex counting (" & result.count & "/" & x & ")"
'--------------some more
Set result = handler.query( _
"{ 'stuff.sex':'male','stuff.age':" & handler.constraints("[['GT',59]]") & "}")
x = 0
For Each job In testData.children
x = x + -1 * (job.child("stuff.sex").value = "male" And job.child("stuff.age").value >= 60)
Next job
assert Array(result.handleCode >= 0, result.count = x), _
result.response, "normal 0 (" & result.count & "/" & x & ")"
'--------------make sure we're getting the right id with complex constaints
Set result = handler.query( _
"{'stuff.age':" & handler.constraints("[['GT',25],['LTE',60]]") & "}")
x = 0
For Each job In testData.children
x = x + -1 * (job.child("stuff.age").value > 25 And job.child("stuff.age").value <= 60)
Next job
assert Array(result.handleCode >= 0, result.count = x), _
result.response, "repeat test constraint (" & result.count & "/" & x & ")"
'--------------try OR
Set result = handler.query( _
"[{'stuff.age':" & handler.constraints("[['LT',26]]") & ",'stuff.sex':'male'}," & _
"{'stuff.age':" & handler.constraints("[['GTE',60]]") & ",'stuff.sex':'male'}]")
x = 0
For Each job In testData.children
x = x + -1 * (job.child("stuff.sex").value = "male" And (job.child("stuff.age").value < 26 Or job.child("stuff.age").value >= 60))
Next job
assert Array(result.handleCode >= 0, result.count = x), _
result.response, "OR 1 (" & result.count & "/" & x & ")"
'------------show all the males
Set r2 = handler.query("{'stuff.sex':'male'}", , 1, 1)
x = 0
For Each job In testData.children
x = x + -1 * CDbl(job.child("stuff.sex").value = "male")
Next job
assert Array(r2.handleCode >= 0, r2.handleKeys.children.count = x), _
r2.response, "show the males(" & r2.count & ")"
'------------remove all the males
Set result = handler.remove("{'stuff.sex':'male'}")
assert Array(result.handleCode >= 0), _
result.response, "remove the males(" & result.count & ")"
'-----------make sure they are gone
Set result = handler.query()
x = 0
For Each job In testData.children
x = x + -1 * CDbl(job.child("stuff.sex").value <> "male")
Next job
assert Array(result.handleCode >= 0, result.handleKeys.children.count = x), _
result.response, "check after delete males(" & result.count & ")"
'-----------add them back in
Set result = handler.save(r2.data)
assert Array(result.handleCode >= 0), _
result.response, "add them back(" & result.count & ")"
'--------check they got added
Set result = handler.query("{'stuff.man':true}")
x = 0
For Each job In testData.children
x = x + -1 * CDbl(job.child("stuff.sex").value = "male")
Next job
assert Array(result.handleCode >= 0, result.handleKeys.children.count = x), _
result.response, "check after adding them back(" & result.count & ")"
'-------sort and save
Set result = handler.query(, "{'sort':'-serial'}")
assert Array(result.handleCode >= 0, result.count = testData.children.count), _
result.response, "sorting serial"
'----- mark as good and save
For Each job In result.data.children
job.add "good", True
Next job
Set r2 = handler.save(result.data)
assert Array(r2.handleCode >= 0), _
r2.response, "adding goods"
'-------check we have twice th records
Set result = handler.count()
assert Array(result.handleCode >= 0, result.count = testData.children.count * 2), _
result.response, "doubled data"
'------delete the ones we added
Set result = handler.remove("{'good':true}")
assert Array(result.handleCode >= 0), _
result.response, "doubled data"
'------check original length
Set result = handler.count()
assert Array(result.handleCode >= 0, result.count = testData.children.count), _
result.response, "check final count"
Debug.Print "Finished " & handler.getDbName
End Sub
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment