Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Objective-C view support for Couchbase Mobile iOS
This is a squashed commit of all the work done on other branches by Jonathon Mah & me. Change-Id: I0b9d020d57502b2ea46b695d9788e42ceb7225fb Reviewed-on: http://review.couchbase.org/10023 Reviewed-by: Jonathon Mah <me@jonathonmah.com> Reviewed-by: Chris Anderson <jchris@couchbase.com> Tested-by: Jens Alfke <jens@couchbase.com>
- Loading branch information
Showing
14 changed files
with
1,497 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
% Licensed under the Apache License, Version 2.0 (the "License"); you may not | ||
% use this file except in compliance with the License. You may obtain a copy of | ||
% the License at | ||
% | ||
% http://www.apache.org/licenses/LICENSE-2.0 | ||
% | ||
% Unless required by applicable law or agreed to in writing, software | ||
% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||
% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||
% License for the specific language governing permissions and limitations under | ||
% the License. | ||
|
||
-module(couch_app_server_objc). | ||
|
||
-export([get_server/3, ret_server/1]). | ||
-export([show_doc/5, validate_update/6, filter_docs/5, filter_view/4]). | ||
-export([list_start/5, list_row/3, list_end/2, update_doc/5]). | ||
|
||
-include("couch_db.hrl"). | ||
|
||
get_server(_Arg, DDoc, DDocKey) -> | ||
objc_dispatch:app_create(DDoc, DDocKey). | ||
|
||
ret_server(_) -> | ||
ok. | ||
|
||
show_doc(Ctx, DDocId, ShowName, Doc, Req) -> | ||
objc_dispatch:app_show_doc(Ctx, DDocId, ShowName, Doc, Req). | ||
|
||
list_start(Ctx, DDocId, ListName, Head, Req) -> | ||
{message, [Chunks, Resp]} = objc_dispatch:app_list_start(Ctx, DDocId, ListName, Head, Req), | ||
{ok, Chunks, Resp}. | ||
|
||
list_row(Ctx, DDocId, Row) -> | ||
objc_dispatch:app_list_row(Ctx, DDocId, Row). | ||
|
||
list_end(Ctx, DDocId) -> | ||
objc_dispatch:app_list_end(Ctx, DDocId). | ||
|
||
update_doc(Ctx, DDocId, UpdateName, Doc, Req) -> | ||
objc_dispatch:app_update_doc(Ctx, DDocId, UpdateName, Doc, Req). | ||
|
||
validate_update(Ctx, DDocId, EditDoc, DiskDoc, Context, SecObj) -> | ||
objc_dispatch:app_validate_update(Ctx, DDocId, EditDoc, DiskDoc, Context, SecObj). | ||
|
||
filter_docs(Ctx, DDocId, FilterName, Docs, Req) -> | ||
objc_dispatch:app_filter_docs(Ctx, DDocId, FilterName, Docs, Req). | ||
|
||
filter_view(Ctx, DDocId, ViewName, Docs) -> | ||
objc_dispatch:app_filter_view(Ctx, DDocId, ViewName, Docs). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
% Licensed under the Apache License, Version 2.0 (the "License"); you may not | ||
% use this file except in compliance with the License. You may obtain a copy of | ||
% the License at | ||
% | ||
% http://www.apache.org/licenses/LICENSE-2.0 | ||
% | ||
% Unless required by applicable law or agreed to in writing, software | ||
% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||
% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||
% License for the specific language governing permissions and limitations under | ||
% the License. | ||
-module(couch_view_server_objc). | ||
|
||
-export([is_lightweight/0, add_view_deps/1]). | ||
-export([get_server/3, ret_server/1]). | ||
-export([map/2, reduce/3, rereduce/3]). | ||
|
||
-include("couch_db.hrl"). | ||
|
||
is_lightweight() -> | ||
true. | ||
|
||
add_view_deps(View) -> | ||
% Catch any error here; we don't really care what it is, only | ||
% that there is an error (so we don't correspond to any other | ||
% pre-computed views). The underlying error will be hit when | ||
% (and if) the view is actually built. | ||
ObjCVers = try objc_dispatch:query_view_version(View#view.def) | ||
catch | ||
_ -> error | ||
end, | ||
[View, ObjCVers]. | ||
|
||
get_server(_Arg, Maps, ReduceStruct) -> | ||
% ReduceStruct is [[ViewId, [RedDef, ...]], [ViewId, [...], ...]. | ||
% Turn this into [RedDef, ...] to decode, and a map of [{ViewId, [3, 4]}, ...] | ||
% where [3,4] are indexes (0-based) into the array of RedDefs. | ||
{RevIndexMap, RevAllRedDefs, _} = lists:foldl(fun([ViewId, RedDefs], {IdIndexAssoc, RedDefAcc, NextIndex}) -> | ||
{NewRedDefs, Indexes, NewNextIndex} = lists:foldl(fun(RedDef, {RedDefs, Indexes, Index}) -> | ||
{[RedDef | RedDefs], [Index | Indexes], Index + 1} | ||
end, {RedDefAcc, [], NextIndex}, RedDefs), | ||
{[{ViewId, Indexes} | IdIndexAssoc], NewRedDefs, NewNextIndex} | ||
end, {[], [], 0}, ReduceStruct), | ||
AllRedDefs = lists:reverse(RevAllRedDefs), | ||
RedIndexMap = [{ViewId, lists:reverse(RevIndexes)} || {ViewId, RevIndexes} <- RevIndexMap], | ||
{ok, QueueRef} = objc_dispatch:create(<<"org.couchdb.objc_view">>, Maps, AllRedDefs), | ||
{ok, {QueueRef, RedIndexMap}}. | ||
|
||
ret_server(_) -> | ||
ok. | ||
|
||
map({QueueRef, _RedIndexMap}, Docs) -> | ||
Results = lists:map(fun(Doc) -> | ||
JsonDoc = couch_doc:to_json_obj(Doc, []), | ||
{ok, EmitsPerView} = objc_dispatch:map(QueueRef, JsonDoc), | ||
lists:map(fun(EmitListItem) -> | ||
[list_to_tuple(EmitPair) || EmitPair <- EmitListItem] | ||
end, EmitsPerView) | ||
end, Docs), | ||
{ok, Results}. | ||
|
||
reduce({QueueRef, RedIndexMap}, ViewId, KVs) -> | ||
Indexes = proplists:get_value(ViewId, RedIndexMap), | ||
{Keys, Vals} = lists:foldl(fun([K, V], {KAcc, VAcc}) -> | ||
{[K | KAcc], [V | VAcc]} | ||
end, {[], []}, KVs), | ||
objc_dispatch:reduce(QueueRef, Indexes, Keys, Vals, false). | ||
|
||
rereduce({QueueRef, RedIndexMap}, ViewId, Vals) -> | ||
Indexes = proplists:get_value(ViewId, RedIndexMap), | ||
objc_dispatch:reduce(QueueRef, Indexes, null, Vals, true). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
// | ||
// CouchbaseAppServer.h | ||
// iErl14 | ||
// | ||
// Created by Jens Alfke on 10/6/11. | ||
// Copyright (c) 2011 Couchbase, Inc. All rights reserved. | ||
// | ||
|
||
#import <Foundation/Foundation.h> | ||
#import "CouchbaseCallbacks.h" | ||
#include "erl_nif.h" | ||
|
||
@interface CouchbaseAppServer : NSObject | ||
{ | ||
ErlNifEnv* _env; | ||
NSDictionary* _designDoc; | ||
CouchValidateUpdateBlock _validateUpdateBlock; | ||
} | ||
|
||
- (id) initWithDesignDoc: (NSDictionary*)designDoc; | ||
|
||
@property ErlNifEnv* env; | ||
|
||
- (BOOL) validateUpdate: (ERL_NIF_TERM)updatedDoc | ||
ofDocument: (ERL_NIF_TERM)currentRevision | ||
context: (ERL_NIF_TERM)context | ||
security: (ERL_NIF_TERM)security | ||
error: (NSDictionary**)outError; | ||
|
||
@end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
// | ||
// CouchbaseAppServer.m | ||
// iErl14 | ||
// | ||
// Created by Jens Alfke on 10/6/11. | ||
// Copyright (c) 2011 Couchbase, Inc. All rights reserved. | ||
// | ||
|
||
#import "CouchbaseAppServer.h" | ||
#import "term_to_objc.h" | ||
|
||
|
||
@interface CouchbaseValidationContext : NSObject <CouchbaseValidationContext> | ||
{ | ||
@private | ||
ErlNifEnv* _env; | ||
ERL_NIF_TERM _currentRevisionTerm, _contextTerm, _securityTerm; | ||
NSDictionary* _currentRevision; | ||
NSDictionary* _contextDict; | ||
NSDictionary* _security; | ||
NSString* _errorType, *_errorMessage; | ||
} | ||
- (id) initWithEnv: (ErlNifEnv*)env | ||
currentRevision: (ERL_NIF_TERM)currentRevision | ||
context: (ERL_NIF_TERM)context | ||
security: (ERL_NIF_TERM)security; | ||
@end | ||
|
||
|
||
@implementation CouchbaseAppServer | ||
|
||
- (id) initWithDesignDoc: (NSDictionary*)designDoc { | ||
self = [super init]; | ||
if (self) { | ||
NSParameterAssert(designDoc); | ||
_designDoc = [designDoc copy]; | ||
} | ||
return self; | ||
} | ||
|
||
- (void)dealloc { | ||
[_validateUpdateBlock release]; | ||
[_designDoc release]; | ||
[super dealloc]; | ||
} | ||
|
||
|
||
@synthesize env = _env; | ||
|
||
|
||
- (BOOL) validateUpdate: (ERL_NIF_TERM)documentTerm | ||
ofDocument: (ERL_NIF_TERM)currentRevisionTerm | ||
context: (ERL_NIF_TERM)contextTerm | ||
security: (ERL_NIF_TERM)securityTerm | ||
error: (NSDictionary**)outError | ||
{ | ||
if (!_validateUpdateBlock) { | ||
NSString* key = [_designDoc objectForKey: @"validate_doc_update"]; | ||
if ([key isKindOfClass: [NSString class]]) | ||
_validateUpdateBlock = [[[CouchbaseCallbacks sharedInstance] | ||
validateUpdateBlockForKey: key] copy]; | ||
|
||
if (!_validateUpdateBlock) | ||
[NSException raise: NSInternalInconsistencyException | ||
format: @"Unregistered native validate_doc_update function '%@'", key]; | ||
} | ||
|
||
NSDictionary* document = term_to_nsdict(_env, documentTerm); | ||
if (!document) | ||
return NO; | ||
|
||
CouchbaseValidationContext* context = [[CouchbaseValidationContext alloc] | ||
initWithEnv: _env | ||
currentRevision: currentRevisionTerm | ||
context: contextTerm | ||
security: securityTerm]; | ||
[context autorelease]; | ||
|
||
if (_validateUpdateBlock(document, context)) | ||
return YES; | ||
|
||
*outError = [NSDictionary dictionaryWithObject: context.errorMessage forKey: context.errorType]; | ||
NSLog(@"Doc failed validation with error {%@: %@}", context.errorType, context.errorMessage); | ||
return NO; | ||
} | ||
|
||
@end | ||
|
||
|
||
|
||
@implementation CouchbaseValidationContext | ||
|
||
|
||
- (id) initWithEnv: (ErlNifEnv*)env | ||
currentRevision: (ERL_NIF_TERM)currentRevision | ||
context: (ERL_NIF_TERM)context | ||
security: (ERL_NIF_TERM)security | ||
{ | ||
self = [super init]; | ||
if (self) { | ||
NSParameterAssert(env); | ||
_env = env; | ||
_currentRevisionTerm = currentRevision; | ||
_contextTerm = context; | ||
_securityTerm = security; | ||
_errorType = [@"forbidden" retain]; | ||
_errorMessage = [@"invalid document" retain]; | ||
} | ||
return self; | ||
} | ||
|
||
- (void)dealloc { | ||
[_errorType release]; | ||
[_errorMessage release]; | ||
[_currentRevision release]; | ||
[_contextDict release]; | ||
[_security release]; | ||
[super dealloc]; | ||
} | ||
|
||
- (NSDictionary*) currentRevision { | ||
if (!_currentRevision) | ||
_currentRevision = [term_to_nsdict(_env, _currentRevisionTerm) retain]; | ||
return _currentRevision; | ||
} | ||
|
||
- (NSDictionary*) contextDict { | ||
if (!_contextDict) | ||
_contextDict = [term_to_nsdict(_env, _contextTerm) retain]; | ||
return _contextDict; | ||
} | ||
|
||
- (NSString*) databaseName { | ||
return [self.contextDict objectForKey: @"db"]; | ||
} | ||
|
||
- (NSString*) userName { | ||
id user = [self.contextDict objectForKey: @"name"]; | ||
if (![user isKindOfClass: [NSString class]]) // anonymous user will be a NSNull object | ||
return nil; | ||
return user; | ||
} | ||
|
||
- (BOOL) isAdmin { | ||
return [[self.contextDict objectForKey: @"roles"] containsObject: @"_admin"]; | ||
} | ||
|
||
- (NSDictionary*) security { | ||
if (!_security) | ||
_security = [term_to_nsdict(_env, _securityTerm) retain]; | ||
return _security; | ||
} | ||
|
||
@synthesize errorType = _errorType, errorMessage = _errorMessage; | ||
|
||
@end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
// | ||
// CouchbaseCallbacks.h | ||
// iErl14 | ||
// | ||
// Created by Jens Alfke on 10/3/11. | ||
// Copyright (c) 2011 Couchbase, Inc. All rights reserved. | ||
// | ||
|
||
#import <Foundation/Foundation.h> | ||
@protocol CouchbaseValidationContext; | ||
|
||
|
||
typedef void (^CouchEmitBlock)(id key, id value); | ||
|
||
/** A "map" function called when a document is to be added to a view. | ||
@param doc The contents of the document being analyzed. | ||
@param emit A block to be called to add a key/value pair to the view. Your block can call zero, one or multiple times. */ | ||
typedef void (^CouchMapBlock)(NSDictionary* doc, CouchEmitBlock emit); | ||
|
||
/** A "reduce" function called to summarize the results of a view. | ||
@param keys An array of keys to be reduced. | ||
@param values A parallel array of values to be reduced, corresponding one-to-one with the keys. | ||
@param rereduce YES if the input keys and values are the results of previous reductions. | ||
@return The reduced value; almost always a scalar or small fixed-size object. */ | ||
typedef id (^CouchReduceBlock)(NSArray* keys, NSArray* values, BOOL rereduce); | ||
|
||
/** Called to validate a document before it's added to the database. | ||
@param doc The submitted document contents. | ||
@param context Lets the block access relevant information and specify an error message. | ||
@return YES to accept the document, NO to reject it. */ | ||
typedef BOOL (^CouchValidateUpdateBlock)(NSDictionary* doc, | ||
id<CouchbaseValidationContext> context); | ||
|
||
/** Central per-process registry for native design-document functions. | ||
Associates a key (a unique ID stored in the design doc as the "source" of the function) | ||
with a C block. | ||
This class is thread-safe. */ | ||
@interface CouchbaseCallbacks : NSObject | ||
{ | ||
NSArray* _registries; | ||
} | ||
|
||
+ (CouchbaseCallbacks*) sharedInstance; | ||
|
||
- (NSString*) generateKey; | ||
|
||
- (void) registerMapBlock: (CouchMapBlock)block forKey: (NSString*)key; | ||
- (void) registerReduceBlock: (CouchReduceBlock)block forKey: (NSString*)key; | ||
- (void) registerValidateUpdateBlock: (CouchValidateUpdateBlock)block forKey: (NSString*)key; | ||
|
||
- (CouchMapBlock) mapBlockForKey: (NSString*)key; | ||
- (CouchReduceBlock) reduceBlockForKey: (NSString*)key; | ||
- (CouchValidateUpdateBlock) validateUpdateBlockForKey: (NSString*)key; | ||
|
||
@end | ||
|
||
|
||
/** Context passed into a CouchValidateUpdateBlock. */ | ||
@protocol CouchbaseValidationContext <NSObject> | ||
|
||
/** The contents of the current revision of the document, or nil if this is a new document. */ | ||
@property (readonly) NSDictionary* currentRevision; | ||
|
||
/** The name of the database being updated. */ | ||
@property (readonly) NSString* databaseName; | ||
|
||
/** The name of the logged-in user, or nil if this is an anonymous request. */ | ||
@property (readonly) NSString* userName; | ||
|
||
/** Does the user have admin privileges? | ||
(If the database is in the default "admin party" mode, this will be YES even when the userName is nil.) */ | ||
@property (readonly) BOOL isAdmin; | ||
|
||
/** The database's security object, which assigns roles and privileges. */ | ||
@property (readonly) NSDictionary* security; | ||
|
||
/** The type of error to report, if the validate block returns NO. | ||
The default value is "forbidden", which will result in an HTTP 403 status. */ | ||
@property (copy) NSString* errorType; | ||
|
||
/** The error message to return in the HTTP response, if the validate block returns NO. | ||
The default value is "invalid document". */ | ||
@property (copy) NSString* errorMessage; | ||
@end |
Oops, something went wrong.