mirror of https://gitee.com/bigwinds/arangodb
Merge branch 'devel' of github.com:arangodb/ArangoDB into devel
This commit is contained in:
commit
bf8da244c2
|
@ -8,7 +8,7 @@ For a full example of sessions with authentication and registration see the exam
|
|||
|
||||
!SECTION Creating an authenticator
|
||||
|
||||
`auth([options]): Authenticator`
|
||||
`createAuth([options]): Authenticator`
|
||||
|
||||
Creates an authenticator.
|
||||
|
||||
|
|
|
@ -0,0 +1,264 @@
|
|||
!CHAPTER OAuth 2.0
|
||||
|
||||
`const createOAuth2Client = require('@arangodb/foxx/oauth2');`
|
||||
|
||||
The OAuth2 module provides abstractions over OAuth2 providers like Facebook, GitHub and Google.
|
||||
|
||||
**Examples**
|
||||
|
||||
The following extends the [user management example](Users.md):
|
||||
|
||||
```js
|
||||
const crypto = require('@arangodb/crypto');
|
||||
const router = createRouter();
|
||||
const oauth2 = createOAuth2Client({
|
||||
// We'll use Facebook for this example
|
||||
authEndpoint: 'https://www.facebook.com/dialog/oauth',
|
||||
tokenEndpoint: 'https://graph.facebook.com/oauth/access_token',
|
||||
activeUserEndpoint: 'https://graph.facebook.com/v2.0/me',
|
||||
clientId: 'keyboardcat',
|
||||
clientSecret: 'keyboardcat'
|
||||
});
|
||||
|
||||
module.context.use('/oauth2', router);
|
||||
|
||||
// See the user management example for setting up the
|
||||
// sessions and users objects used in this example
|
||||
router.use(sessions);
|
||||
|
||||
router.post('/auth', function (req, res) {
|
||||
const csrfToken = crypto.genRandomAlphaNumbers(32);
|
||||
const url = req.reverse('oauth2_callback', {csrfToken});
|
||||
const redirect_uri = req.makeAbsolute(url);
|
||||
// Set CSRF cookie for five minutes
|
||||
res.cookie('oauth2_csrf_token', csrfToken, {ttl: 60 * 5});
|
||||
// Redirect to the provider's authorization URL
|
||||
res.redirect(303, oauth2.getAuthUrl(url));
|
||||
});
|
||||
|
||||
router.get('/auth', function (req, res) {
|
||||
// Some providers pass errors as query parameter
|
||||
if (req.queryParams.error) {
|
||||
res.throw(500, `Provider error: ${req.queryParams.error}`)
|
||||
}
|
||||
// Make sure CSRF cookie matches the URL
|
||||
const expectedToken = req.cookie('oauth2_csrf_token');
|
||||
if (!expectedToken || req.queryParams.csrfToken !== expectedToken) {
|
||||
res.throw(400, 'CSRF mismatch.');
|
||||
}
|
||||
// Make sure the URL contains a grant token
|
||||
if (!req.queryParams.code) {
|
||||
res.throw(400, 'Provider did not pass grant token.');
|
||||
}
|
||||
// Reconstruct the redirect_uri used for the grant token
|
||||
const url = req.reverse('oauth2_callback');
|
||||
const redirect_uri = req.makeAbsolute(url);
|
||||
// Fetch an access token from the provider
|
||||
const authData = oauth2.exchangeGrantToken(
|
||||
req.queryParams.code,
|
||||
redirect_uri
|
||||
);
|
||||
const facebookToken = authData.access_token;
|
||||
// Fetch the active user's profile info
|
||||
const profile = oauth2.fetchActiveUser(facebookToken);
|
||||
const facebookId = profile.id;
|
||||
// Try to find an existing user with the user ID
|
||||
// (this requires the users collection)
|
||||
let user = users.firstExample({facebookId});
|
||||
if (user) {
|
||||
// Update the access_token if it has changed
|
||||
if (user.facebookToken !== facebookToken) {
|
||||
users.update(user, {facebookToken});
|
||||
}
|
||||
} else {
|
||||
// Create a new user document
|
||||
user = {
|
||||
username: `fb:${facebookId}`,
|
||||
facebookId,
|
||||
access_token
|
||||
}
|
||||
const meta = users.save(user);
|
||||
Object.assign(user, meta);
|
||||
}
|
||||
// Log the user in (this requires the session middleware)
|
||||
req.session.uid = user._key;
|
||||
req.session.access_token = authData.access_token;
|
||||
req.sessionStorage.save(req.session);
|
||||
// Redirect to the default route
|
||||
res.redirect(303, req.makeAbsolute('/'));
|
||||
}, 'oauth2_callback')
|
||||
.queryParam('error', joi.string().optional())
|
||||
.queryParam('csrfToken', joi.string().optional())
|
||||
.queryParam('code', joi.string().optional());
|
||||
```
|
||||
|
||||
!SECTION Creating an OAuth2 client
|
||||
|
||||
`createOAuth2Client(options): OAuth2Client`
|
||||
|
||||
Creates an OAuth2 client.
|
||||
|
||||
**Arguments**
|
||||
|
||||
* **options**: `Object`
|
||||
|
||||
An object with the following properties:
|
||||
|
||||
* **authEndpoint**: `string`
|
||||
|
||||
The fully-qualified URL of the provider's [authorization endpoint](http://tools.ietf.org/html/rfc6749#section-3.1).
|
||||
|
||||
* **tokenEndpoint**: `string`
|
||||
|
||||
The fully-qualified URL of the provider's [token endpoint](http://tools.ietf.org/html/rfc6749#section-3.2).
|
||||
|
||||
* **refreshEndpoint**: `string` (optional)
|
||||
|
||||
The fully-qualified URL of the provider's [refresh token endpoint](http://tools.ietf.org/html/rfc6749#section-6).
|
||||
|
||||
* **activeUserEndpoint**: `string` (optional)
|
||||
|
||||
The fully-qualified URL of the provider's endpoint for fetching details about the current user.
|
||||
|
||||
* **clientId**: `string`
|
||||
|
||||
The application's *Client ID* (or *App ID*) for the provider.
|
||||
|
||||
* **clientSecret**: `string`
|
||||
|
||||
The application's *Client Secret* (or *App Secret*) for the provider.
|
||||
|
||||
Returns an OAuth2 client for the given provider.
|
||||
|
||||
!SUBSECTION Setting up OAuth2 for Facebook
|
||||
|
||||
If you want to use Facebook as the OAuth2 provider, use the following options:
|
||||
|
||||
* *authEndpoint*: `https://www.facebook.com/dialog/oauth`
|
||||
* *tokenEndpoint*: `https://graph.facebook.com/oauth/access_token`
|
||||
* *activeUserEndpoint*: `https://graph.facebook.com/v2.0/me`
|
||||
|
||||
You also need to obtain a client ID and client secret from Facebook:
|
||||
|
||||
1. Create a regular account at [Facebook](https://www.facebook.com) or use an existing account you own.
|
||||
2. Visit the [Facebook Developers](https://developers.facebook.com) page.
|
||||
3. Click on *Apps* in the menu, then select *Register as a Developer* (the only option) and follow the instructions provided. You may need to verify your account by phone.
|
||||
4. Click on *Apps* in the menu, then select *Create a New App* and follow the instructions provided.
|
||||
5. Open the app dashboard, then note down the *App ID* and *App Secret*. The secret may be hidden by default.
|
||||
6. Click on *Settings*, then *Advanced* and enter one or more *Valid OAuth redirect URIs*. At least one of them must match your *redirect_uri* later. Don't forget to save your changes.
|
||||
7. Set the option *clientId* to the *App ID* and the option *clientSecret* to the *App Secret*.
|
||||
|
||||
!SUBSECTION Setting up OAuth2 for GitHub
|
||||
|
||||
If you want to use GitHub as the OAuth2 provider, use the following options:
|
||||
|
||||
* *authEndpoint*: `https://github.com/login/oauth/authorize?scope=user`
|
||||
* *tokenEndpoint*: `https://github.com/login/oauth/access_token`
|
||||
* *activeUserEndpoint*: `https://api.github.com/user`
|
||||
|
||||
You also need to obtain a client ID and client secret from GitHub:
|
||||
|
||||
1. Create a regular account at [GitHub](https://github.com) or use an existing account you own.
|
||||
2. Go to [Account Settings > Applications > Register new application](https://github.com/settings/applications/new).
|
||||
3. Provide an *authorization callback URL*. This must match your *redirect_uri* later.
|
||||
4. Fill in the other required details and follow the instructions provided.
|
||||
5. Open the application page, then note down the *Client ID* and *Client Secret*.
|
||||
6. Set the option *clientId* to the *Client ID* and the option *clientSecret* to the *Client Secret*.
|
||||
|
||||
!SUBSECTION Setting up OAuth2 for Google
|
||||
|
||||
If you want to use Google as the OAuth2 provider, use the following options:
|
||||
|
||||
* *authEndpoint*: `https://accounts.google.com/o/oauth2/auth?access_type=offline&scope=profile`
|
||||
* *tokenEndpoint*: `https://accounts.google.com/o/oauth2/token`
|
||||
* *activeUserEndpoint*: `https://www.googleapis.com/plus/v1/people/me`
|
||||
|
||||
You also need to obtain a client ID and client secret from Google:
|
||||
|
||||
1. Create a regular account at [Google](https://www.google.com) or use an existing account you own.
|
||||
2. Visit the [Google Developers Console](https://console.developers.google.com).
|
||||
3. Click on *Create Project*, then follow the instructions provided.
|
||||
4. When your project is ready, open the project dashboard, then click on *Enable an API*.
|
||||
5. Enable the *Google+ API* to allow your app to distinguish between different users.
|
||||
6. Open the *Credentials* page and click *Create new Client ID*, then follow the instructions provided. At least one *Authorized Redirect URI* must match your *redirect_uri* later. At least one *Authorized JavaScript Origin* must match your app's fully-qualified domain.
|
||||
7. When the Client ID is ready, note down the *Client ID* and *Client secret*.
|
||||
8. Set the option *clientId* to the *Client ID* and the option *clientSecret* to the *Client secret*.
|
||||
|
||||
!SECTION Get the authorization URL
|
||||
|
||||
`oauth2.getAuthUrl(redirect_uri, args): string`
|
||||
|
||||
Generates the authorization URL for the authorization endpoint.
|
||||
|
||||
**Arguments**
|
||||
|
||||
* **redirect_uri**: `string`
|
||||
|
||||
The fully-qualified URL of your application's OAuth2 callback.
|
||||
|
||||
* **args**: (optional)
|
||||
|
||||
An object with any of the following properties:
|
||||
|
||||
* **response_type**: `string` (Default: `"code"`)
|
||||
|
||||
See [RFC 6749](http://tools.ietf.org/html/rfc6749).
|
||||
|
||||
Returns a fully-qualified URL for the authorization endpoint of the provider by appending the client ID and any additional arguments from *args* to the *authEndpoint*.
|
||||
|
||||
!SECTION Exchange a grant code for an access token
|
||||
|
||||
`oauth2.exchangeGrantToken(code, redirect_uri)`
|
||||
|
||||
Exchanges a grant code for an access token.
|
||||
|
||||
Performs a *POST* response to the *tokenEndpoint*.
|
||||
|
||||
Throws an exception if the remote server responds with an empty response body.
|
||||
|
||||
**Arguments**
|
||||
|
||||
* **code**: `string`
|
||||
|
||||
A grant code returned by the provider's authorization endpoint.
|
||||
|
||||
* **redirect_uri**: `string`
|
||||
|
||||
The original callback URL with which the code was requested.
|
||||
|
||||
* **args**: `Object` (optional)
|
||||
|
||||
An object with any of the following properties:
|
||||
|
||||
* **grant_type**: `string` (Default: `"authorization_code"`)
|
||||
|
||||
See [RFC 6749](http://tools.ietf.org/html/rfc6749).
|
||||
|
||||
Returns the parsed response object.
|
||||
|
||||
!SECTION Fetch the active user
|
||||
|
||||
`oauth2.fetchActiveUser(access_token): Object`
|
||||
|
||||
Fetches details of the active user.
|
||||
|
||||
Performs a *GET* response to the *activeUserEndpoint*.
|
||||
|
||||
Throws an exception if the remote server responds with an empty response body.
|
||||
|
||||
Also throws an exception if the *activeUserEndpoint* is not configured.
|
||||
|
||||
**Arguments**
|
||||
|
||||
* **access_token**: `string`
|
||||
|
||||
An OAuth2 access token as returned by *exchangeGrantToken*.
|
||||
|
||||
Returns the parsed response object.
|
||||
|
||||
**Examples**
|
||||
|
||||
```js
|
||||
const authData = oauth2.exchangeGrantToken(code, redirect_uri);
|
||||
const userData = oauth2.fetchActiveUser(authData.access_token);
|
||||
```
|
|
@ -106,7 +106,7 @@
|
|||
* [User management](Foxx/Users.md)
|
||||
* [Related modules](Foxx/Modules.md)
|
||||
* [Authentication](Foxx/Auth.md)
|
||||
# * [OAuth 2.0](Foxx/OAuth2.md)
|
||||
* [OAuth 2.0](Foxx/OAuth2.md)
|
||||
* [Transactions](Transactions/README.md)
|
||||
* [Transaction invocation](Transactions/TransactionInvocation.md)
|
||||
* [Passing parameters](Transactions/Passing.md)
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
!CHAPTER The OAuth2 module
|
||||
|
||||
TODO
|
||||
|
|
@ -54,6 +54,45 @@ static std::unique_ptr<VPackAttributeTranslator> Translator;
|
|||
static std::unique_ptr<VPackAttributeExcludeHandler> ExcludeHandler;
|
||||
static std::unique_ptr<VPackCustomTypeHandler> CustomTypeHandler;
|
||||
|
||||
// statically computed table of type weights
|
||||
// the weight for type MinKey must be lowest, the weight for type MaxKey must be highest
|
||||
// the table contains a special value -50 to indicate that the value is an external which must be resolved further
|
||||
// the type Custom has the same weight as the String type, because the Custom type is used to store _id (which is a string)
|
||||
static int8_t const TypeWeights[256] = {
|
||||
0 /* 0x00 */, 4 /* 0x01 */, 4 /* 0x02 */, 4 /* 0x03 */, 4 /* 0x04 */, 4 /* 0x05 */, 4 /* 0x06 */, 4 /* 0x07 */,
|
||||
4 /* 0x08 */, 4 /* 0x09 */, 5 /* 0x0a */, 5 /* 0x0b */, 5 /* 0x0c */, 5 /* 0x0d */, 5 /* 0x0e */, 5 /* 0x0f */,
|
||||
5 /* 0x10 */, 5 /* 0x11 */, 5 /* 0x12 */, 4 /* 0x13 */, 5 /* 0x14 */, 0 /* 0x15 */, 0 /* 0x16 */, -1 /* 0x17 */,
|
||||
0 /* 0x18 */, 1 /* 0x19 */, 1 /* 0x1a */, 2 /* 0x1b */, 2 /* 0x1c */, -50 /* 0x1d */, -99 /* 0x1e */, 99 /* 0x1f */,
|
||||
2 /* 0x20 */, 2 /* 0x21 */, 2 /* 0x22 */, 2 /* 0x23 */, 2 /* 0x24 */, 2 /* 0x25 */, 2 /* 0x26 */, 2 /* 0x27 */,
|
||||
2 /* 0x28 */, 2 /* 0x29 */, 2 /* 0x2a */, 2 /* 0x2b */, 2 /* 0x2c */, 2 /* 0x2d */, 2 /* 0x2e */, 2 /* 0x2f */,
|
||||
2 /* 0x30 */, 2 /* 0x31 */, 2 /* 0x32 */, 2 /* 0x33 */, 2 /* 0x34 */, 2 /* 0x35 */, 2 /* 0x36 */, 2 /* 0x37 */,
|
||||
2 /* 0x38 */, 2 /* 0x39 */, 2 /* 0x3a */, 2 /* 0x3b */, 2 /* 0x3c */, 2 /* 0x3d */, 2 /* 0x3e */, 2 /* 0x3f */,
|
||||
3 /* 0x40 */, 3 /* 0x41 */, 3 /* 0x42 */, 3 /* 0x43 */, 3 /* 0x44 */, 3 /* 0x45 */, 3 /* 0x46 */, 3 /* 0x47 */,
|
||||
3 /* 0x48 */, 3 /* 0x49 */, 3 /* 0x4a */, 3 /* 0x4b */, 3 /* 0x4c */, 3 /* 0x4d */, 3 /* 0x4e */, 3 /* 0x4f */,
|
||||
3 /* 0x50 */, 3 /* 0x51 */, 3 /* 0x52 */, 3 /* 0x53 */, 3 /* 0x54 */, 3 /* 0x55 */, 3 /* 0x56 */, 3 /* 0x57 */,
|
||||
3 /* 0x58 */, 3 /* 0x59 */, 3 /* 0x5a */, 3 /* 0x5b */, 3 /* 0x5c */, 3 /* 0x5d */, 3 /* 0x5e */, 3 /* 0x5f */,
|
||||
3 /* 0x60 */, 3 /* 0x61 */, 3 /* 0x62 */, 3 /* 0x63 */, 3 /* 0x64 */, 3 /* 0x65 */, 3 /* 0x66 */, 3 /* 0x67 */,
|
||||
3 /* 0x68 */, 3 /* 0x69 */, 3 /* 0x6a */, 3 /* 0x6b */, 3 /* 0x6c */, 3 /* 0x6d */, 3 /* 0x6e */, 3 /* 0x6f */,
|
||||
3 /* 0x70 */, 3 /* 0x71 */, 3 /* 0x72 */, 3 /* 0x73 */, 3 /* 0x74 */, 3 /* 0x75 */, 3 /* 0x76 */, 3 /* 0x77 */,
|
||||
3 /* 0x78 */, 3 /* 0x79 */, 3 /* 0x7a */, 3 /* 0x7b */, 3 /* 0x7c */, 3 /* 0x7d */, 3 /* 0x7e */, 3 /* 0x7f */,
|
||||
3 /* 0x80 */, 3 /* 0x81 */, 3 /* 0x82 */, 3 /* 0x83 */, 3 /* 0x84 */, 3 /* 0x85 */, 3 /* 0x86 */, 3 /* 0x87 */,
|
||||
3 /* 0x88 */, 3 /* 0x89 */, 3 /* 0x8a */, 3 /* 0x8b */, 3 /* 0x8c */, 3 /* 0x8d */, 3 /* 0x8e */, 3 /* 0x8f */,
|
||||
3 /* 0x90 */, 3 /* 0x91 */, 3 /* 0x92 */, 3 /* 0x93 */, 3 /* 0x94 */, 3 /* 0x95 */, 3 /* 0x96 */, 3 /* 0x97 */,
|
||||
3 /* 0x98 */, 3 /* 0x99 */, 3 /* 0x9a */, 3 /* 0x9b */, 3 /* 0x9c */, 3 /* 0x9d */, 3 /* 0x9e */, 3 /* 0x9f */,
|
||||
3 /* 0xa0 */, 3 /* 0xa1 */, 3 /* 0xa2 */, 3 /* 0xa3 */, 3 /* 0xa4 */, 3 /* 0xa5 */, 3 /* 0xa6 */, 3 /* 0xa7 */,
|
||||
3 /* 0xa8 */, 3 /* 0xa9 */, 3 /* 0xaa */, 3 /* 0xab */, 3 /* 0xac */, 3 /* 0xad */, 3 /* 0xae */, 3 /* 0xaf */,
|
||||
3 /* 0xb0 */, 3 /* 0xb1 */, 3 /* 0xb2 */, 3 /* 0xb3 */, 3 /* 0xb4 */, 3 /* 0xb5 */, 3 /* 0xb6 */, 3 /* 0xb7 */,
|
||||
3 /* 0xb8 */, 3 /* 0xb9 */, 3 /* 0xba */, 3 /* 0xbb */, 3 /* 0xbc */, 3 /* 0xbd */, 3 /* 0xbe */, 3 /* 0xbf */,
|
||||
3 /* 0xc0 */, 3 /* 0xc1 */, 3 /* 0xc2 */, 3 /* 0xc3 */, 3 /* 0xc4 */, 3 /* 0xc5 */, 3 /* 0xc6 */, 3 /* 0xc7 */,
|
||||
2 /* 0xc8 */, 2 /* 0xc9 */, 2 /* 0xca */, 2 /* 0xcb */, 2 /* 0xcc */, 2 /* 0xcd */, 2 /* 0xce */, 2 /* 0xcf */,
|
||||
2 /* 0xd0 */, 2 /* 0xd1 */, 2 /* 0xd2 */, 2 /* 0xd3 */, 2 /* 0xd4 */, 2 /* 0xd5 */, 2 /* 0xd6 */, 2 /* 0xd7 */,
|
||||
0 /* 0xd8 */, 0 /* 0xd9 */, 0 /* 0xda */, 0 /* 0xdb */, 0 /* 0xdc */, 0 /* 0xdd */, 0 /* 0xde */, 0 /* 0xdf */,
|
||||
0 /* 0xe0 */, 0 /* 0xe1 */, 0 /* 0xe2 */, 0 /* 0xe3 */, 0 /* 0xe4 */, 0 /* 0xe5 */, 0 /* 0xe6 */, 0 /* 0xe7 */,
|
||||
0 /* 0xe8 */, 0 /* 0xe9 */, 0 /* 0xea */, 0 /* 0xeb */, 0 /* 0xec */, 0 /* 0xed */, 0 /* 0xee */, 0 /* 0xef */,
|
||||
3 /* 0xf0 */, 3 /* 0xf1 */, 3 /* 0xf2 */, 3 /* 0xf3 */, 3 /* 0xf4 */, 3 /* 0xf5 */, 3 /* 0xf6 */, 3 /* 0xf7 */,
|
||||
3 /* 0xf8 */, 3 /* 0xf9 */, 3 /* 0xfa */, 3 /* 0xfb */, 3 /* 0xfc */, 3 /* 0xfd */, 3 /* 0xfe */, 3 /* 0xff */,
|
||||
};
|
||||
|
||||
// a default custom type handler that prevents throwing exceptions when
|
||||
// custom types are encountered during Slice.toJson() and family
|
||||
struct DefaultCustomTypeHandler final : public VPackCustomTypeHandler {
|
||||
|
@ -289,43 +328,14 @@ bool VelocyPackHelper::VPackIdEqual::operator()(VPackSlice const& lhs,
|
|||
0);
|
||||
};
|
||||
|
||||
|
||||
|
||||
static int TypeWeight(VPackSlice const& slice) {
|
||||
switch (slice.type()) {
|
||||
case VPackValueType::MinKey:
|
||||
return -99; // must be lowest
|
||||
case VPackValueType::Illegal:
|
||||
return -1;
|
||||
case VPackValueType::None:
|
||||
case VPackValueType::Null:
|
||||
return 0;
|
||||
case VPackValueType::Bool:
|
||||
return 1;
|
||||
case VPackValueType::Double:
|
||||
case VPackValueType::Int:
|
||||
case VPackValueType::UInt:
|
||||
case VPackValueType::SmallInt:
|
||||
case VPackValueType::UTCDate:
|
||||
case VPackValueType::BCD:
|
||||
return 2;
|
||||
case VPackValueType::String:
|
||||
case VPackValueType::Binary:
|
||||
case VPackValueType::Custom:
|
||||
// custom type is used for _id (which is a string)
|
||||
return 3;
|
||||
case VPackValueType::Array:
|
||||
return 4;
|
||||
case VPackValueType::Object:
|
||||
return 5;
|
||||
case VPackValueType::External:
|
||||
return TypeWeight(slice.resolveExternal());
|
||||
case VPackValueType::MaxKey:
|
||||
return 99; // must be highest
|
||||
default:
|
||||
// All other values have equal weight
|
||||
return 0;
|
||||
static inline int8_t TypeWeight(VPackSlice& slice) {
|
||||
again:
|
||||
int8_t w = TypeWeights[slice.head()];
|
||||
if (w == -50) {
|
||||
slice = slice.resolveExternal();
|
||||
goto again;
|
||||
}
|
||||
return w;
|
||||
}
|
||||
|
||||
int VelocyPackHelper::compareNumberValues(VPackValueType lhsType,
|
||||
|
@ -361,6 +371,33 @@ int VelocyPackHelper::compareNumberValues(VPackValueType lhsType,
|
|||
}
|
||||
return (left < right ? -1 : 1);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief compares two VelocyPack string values
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
int VelocyPackHelper::compareStringValues(char const* left, VPackValueLength nl, char const* right, VPackValueLength nr, bool useUTF8) {
|
||||
int res;
|
||||
if (useUTF8) {
|
||||
res = TRI_compare_utf8(left, static_cast<size_t>(nl), right,
|
||||
static_cast<size_t>(nr));
|
||||
} else {
|
||||
size_t len = static_cast<size_t>(nl < nr ? nl : nr);
|
||||
res = memcmp(left, right, len);
|
||||
}
|
||||
if (res < 0) {
|
||||
return -1;
|
||||
}
|
||||
if (res > 0) {
|
||||
return 1;
|
||||
}
|
||||
// res == 0
|
||||
if (nl == nr) {
|
||||
return 0;
|
||||
}
|
||||
// res == 0, but different string lengths
|
||||
return nl < nr ? -1 : 1;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief returns a boolean sub-element, or a default if it is does not exist
|
||||
|
@ -636,9 +673,9 @@ int VelocyPackHelper::compare(VPackSlice lhs, VPackSlice rhs, bool useUTF8,
|
|||
VPackSlice const* lhsBase,
|
||||
VPackSlice const* rhsBase) {
|
||||
{
|
||||
// will resolve externals...
|
||||
int lWeight = TypeWeight(lhs);
|
||||
int rWeight = TypeWeight(rhs);
|
||||
// will resolve externals and modify both lhs & rhs...
|
||||
int8_t lWeight = TypeWeight(lhs);
|
||||
int8_t rWeight = TypeWeight(rhs);
|
||||
|
||||
if (lWeight < rWeight) {
|
||||
return -1;
|
||||
|
@ -651,19 +688,19 @@ int VelocyPackHelper::compare(VPackSlice lhs, VPackSlice rhs, bool useUTF8,
|
|||
TRI_ASSERT(lWeight == rWeight);
|
||||
}
|
||||
|
||||
lhs = lhs.resolveExternal(); // follow externals
|
||||
rhs = rhs.resolveExternal(); // follow externals
|
||||
|
||||
// lhs and rhs have equal weights
|
||||
if (lhs.isNone() || rhs.isNone()) {
|
||||
// either lhs or rhs is none. we cannot be sure here that both are
|
||||
// nones.
|
||||
// there can also exist the situation that lhs is a none and rhs is a
|
||||
// null value
|
||||
// (or vice versa). Anyway, the compare value is the same for both,
|
||||
return 0;
|
||||
}
|
||||
|
||||
// note that the following code would be redundant because it was already
|
||||
// checked that lhs & rhs have the same TypeWeight, which is 0 for none.
|
||||
// and for TypeWeight 0 we always return value 0
|
||||
// if (lhs.isNone() || rhs.isNone()) {
|
||||
// // if rhs is none. we cannot be sure here that both are nones.
|
||||
// // there can also exist the situation that lhs is a none and rhs is a
|
||||
// // null value
|
||||
// // (or vice versa). Anyway, the compare value is the same for both,
|
||||
// return 0;
|
||||
// }
|
||||
|
||||
auto lhsType = lhs.type();
|
||||
|
||||
switch (lhsType) {
|
||||
|
@ -679,7 +716,7 @@ int VelocyPackHelper::compare(VPackSlice lhs, VPackSlice rhs, bool useUTF8,
|
|||
if (left == right) {
|
||||
return 0;
|
||||
}
|
||||
if (!left && right) {
|
||||
if (!left) {
|
||||
return -1;
|
||||
}
|
||||
return 1;
|
||||
|
@ -690,84 +727,60 @@ int VelocyPackHelper::compare(VPackSlice lhs, VPackSlice rhs, bool useUTF8,
|
|||
case VPackValueType::SmallInt: {
|
||||
return compareNumberValues(lhsType, lhs, rhs);
|
||||
}
|
||||
case VPackValueType::Custom:
|
||||
case VPackValueType::String: {
|
||||
std::string lhsString;
|
||||
VPackValueLength nl;
|
||||
char const* left;
|
||||
if (lhs.isCustom()) {
|
||||
if (lhsBase == nullptr || options == nullptr ||
|
||||
options->customTypeHandler == nullptr) {
|
||||
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL,
|
||||
"Could not extract custom attribute.");
|
||||
}
|
||||
lhsString.assign(
|
||||
options->customTypeHandler->toString(lhs, options, *lhsBase));
|
||||
left = lhsString.c_str();
|
||||
nl = lhsString.size();
|
||||
} else {
|
||||
left = lhs.getString(nl);
|
||||
}
|
||||
char const* left = lhs.getString(nl);
|
||||
TRI_ASSERT(left != nullptr);
|
||||
|
||||
std::string rhsString;
|
||||
VPackValueLength nr;
|
||||
char const* right;
|
||||
if (rhs.isCustom()) {
|
||||
if (rhsBase == nullptr || options == nullptr ||
|
||||
options->customTypeHandler == nullptr) {
|
||||
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL,
|
||||
"Could not extract custom attribute.");
|
||||
}
|
||||
rhsString.assign(
|
||||
options->customTypeHandler->toString(rhs, options, *rhsBase));
|
||||
right = rhsString.c_str();
|
||||
nr = rhsString.size();
|
||||
} else {
|
||||
right = rhs.getString(nr);
|
||||
}
|
||||
char const* right = rhs.getString(nr);
|
||||
TRI_ASSERT(right != nullptr);
|
||||
|
||||
int res;
|
||||
if (useUTF8) {
|
||||
res = TRI_compare_utf8(left, static_cast<size_t>(nl), right,
|
||||
static_cast<size_t>(nr));
|
||||
} else {
|
||||
size_t len = static_cast<size_t>(nl < nr ? nl : nr);
|
||||
res = memcmp(left, right, len);
|
||||
return compareStringValues(left, nl, right, nr, useUTF8);
|
||||
}
|
||||
case VPackValueType::Custom: {
|
||||
if (lhsBase == nullptr || rhsBase == nullptr ||
|
||||
options == nullptr ||
|
||||
options->customTypeHandler == nullptr) {
|
||||
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL,
|
||||
"Could not extract custom attribute.");
|
||||
}
|
||||
if (res < 0) {
|
||||
return -1;
|
||||
}
|
||||
if (res > 0) {
|
||||
return 1;
|
||||
}
|
||||
// res == 0
|
||||
if (nl == nr) {
|
||||
return 0;
|
||||
}
|
||||
// res == 0, but different string lengths
|
||||
return nl < nr ? -1 : 1;
|
||||
std::string lhsString(options->customTypeHandler->toString(lhs, options, *lhsBase));
|
||||
char const* left = lhsString.c_str();
|
||||
VPackValueLength nl = lhsString.size();
|
||||
TRI_ASSERT(left != nullptr);
|
||||
|
||||
std::string rhsString(options->customTypeHandler->toString(rhs, options, *rhsBase));
|
||||
char const* right = rhsString.c_str();
|
||||
VPackValueLength nr = rhsString.size();
|
||||
TRI_ASSERT(right != nullptr);
|
||||
|
||||
return compareStringValues(left, nl, right, nr, useUTF8);
|
||||
}
|
||||
case VPackValueType::Array: {
|
||||
VPackValueLength const nl = lhs.length();
|
||||
VPackValueLength const nr = rhs.length();
|
||||
VPackValueLength const n = (std::max)(nr, nl);
|
||||
VPackArrayIterator al(lhs);
|
||||
VPackArrayIterator ar(rhs);
|
||||
|
||||
VPackValueLength const n = (std::max)(al.size(), ar.size());
|
||||
for (VPackValueLength i = 0; i < n; ++i) {
|
||||
VPackSlice lhsValue;
|
||||
if (i < nl) {
|
||||
lhsValue = lhs.at(i).resolveExternal();
|
||||
}
|
||||
VPackSlice rhsValue;
|
||||
if (i < nr) {
|
||||
rhsValue = rhs.at(i).resolveExternal();
|
||||
}
|
||||
|
||||
if (i < al.size()) {
|
||||
lhsValue = al.value();
|
||||
al.next();
|
||||
}
|
||||
if (i < ar.size()) {
|
||||
rhsValue = ar.value();
|
||||
ar.next();
|
||||
}
|
||||
|
||||
int result = compare(lhsValue, rhsValue, useUTF8, options, &lhs, &rhs);
|
||||
if (result != 0) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
case VPackValueType::Object: {
|
||||
|
|
|
@ -290,6 +290,14 @@ class VelocyPackHelper {
|
|||
arangodb::velocypack::Slice lhs,
|
||||
arangodb::velocypack::Slice rhs);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief compares two VelocyPack string values
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static int compareStringValues(char const* left, VPackValueLength nl,
|
||||
char const* right, VPackValueLength nr,
|
||||
bool useUTF8);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief Compares two VelocyPack slices
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
|
Loading…
Reference in New Issue