2
0
mirror of https://github.com/boostorg/redis.git synced 2026-01-27 19:22:07 +00:00

Compare commits

...

106 Commits

Author SHA1 Message Date
Marcelo Zimbres
394bdf5b5e Increases the version. 2022-05-01 12:41:47 +02:00
Marcelo Zimbres
a4745a1a5d Better docs. 2022-05-01 12:34:21 +02:00
Marcelo Zimbres
b99da962d1 More improvements in the docs. 2022-05-01 10:48:03 +02:00
Marcelo Zimbres
172de6235c Code simplification. 2022-05-01 08:31:00 +02:00
Marcelo Zimbres
5e99a58685 Fixes the echo_server example. 2022-04-30 22:23:31 +02:00
Marcelo Zimbres
b952a2d2d8 Fixes async_op. 2022-04-30 22:21:36 +02:00
Marcelo Zimbres
16d1f8df24 Commit of
- Documentation.
- Avoids temporaries on connect.
- Removes many unnecessary instantiations of the serializer.
- Fixes ping operation.
2022-04-30 13:09:41 +02:00
Marcelo Zimbres
82b804e397 Increases the version. 2022-04-28 10:56:42 +02:00
Marcelo Zimbres
22530724c6 Improvements in the documentation. 2022-04-28 10:49:19 +02:00
Marcelo Zimbres
610aef5c5e Comparison with redis-plus-plus. 2022-04-27 23:09:27 +02:00
Marcelo Zimbres
87a03a0b6b Moves response_traits to detail. 2022-04-27 10:07:06 +02:00
Marcelo Zimbres
0e3de3688e Improvements in the docs. 2022-04-26 17:58:52 +02:00
Marcelo Zimbres
980c39084f Removes some functions and improves docs. 2022-04-26 15:51:47 +02:00
Marcelo Zimbres
abd339d0f2 Updates the copyright notice. 2022-04-26 10:43:35 +02:00
Marcelo Zimbres
a86a18c969 Using small_vector. 2022-04-26 10:38:14 +02:00
Marcelo Zimbres
f0deda42a4 Updates to boost license. 2022-04-25 22:16:05 +02:00
Marcelo Zimbres
d1e7ec6f03 Removes unused function. 2022-04-25 22:00:16 +02:00
Marcelo Zimbres
0e8e31f310 Replaces all pragma onces with guards. 2022-04-25 14:34:10 +02:00
Marcelo Zimbres
ba86c9fb05 Replaces some accurrences of pragma once with guards. 2022-04-25 11:43:49 +02:00
Marcelo Zimbres
ccef30c8ac Uses boost assert. 2022-04-25 11:28:08 +02:00
Marcelo Zimbres
ce77524d6f Simplifies client code. 2022-04-25 10:53:22 +02:00
Marcelo Zimbres
54a9fddc40 Fixes test. 2022-04-25 09:59:13 +02:00
Marcelo Zimbres
ad3127d1ca Adds function set_receiver. 2022-04-25 09:09:31 +02:00
Marcelo Zimbres
0363353154 Implements async_ping. 2022-04-24 17:37:17 +02:00
Marcelo Zimbres
b453fc63c9 Adds test for idle timeout. 2022-04-24 15:31:26 +02:00
Marcelo Zimbres
1a47dece35 Implements the idle timeout. 2022-04-24 15:10:37 +02:00
Marcelo Zimbres
048a711e51 Simplifies the code. 2022-04-24 11:42:29 +02:00
Marcelo Zimbres
a940f7f4bf Adds test to discard. 2022-04-23 16:37:53 +02:00
Marcelo Zimbres
fd0cae92ee Fixes usage of executors and adds test. 2022-04-22 15:30:36 +02:00
Marcelo Zimbres
a5376bc05f Support reconnect on the client. 2022-04-22 13:36:34 +02:00
Marcelo Zimbres
98580eb0ea Adds test and fixes bug. 2022-04-21 21:35:35 +02:00
Marcelo Zimbres
6ed2d96b07 Adds some tests to the high-level client. 2022-04-21 16:01:31 +02:00
Marcelo Zimbres
245fdb55b6 Adds resolve operation to the client. 2022-04-21 12:01:40 +02:00
Marcelo Zimbres
e6443cbe26 Fixes the documentation. 2022-04-20 16:00:43 +02:00
Marcelo Zimbres
2101def89f Fixes the makefile. 2022-04-20 14:37:51 +02:00
Marcelo Zimbres
ddfe9defc5 More improvements in the examples. 2022-04-20 12:21:10 +02:00
Marcelo Zimbres
cd28ff285f Removes receiver concept and uses callbacks. 2022-04-20 11:46:28 +02:00
Marcelo Zimbres
c4bd338e79 Fixes bug in queue managements. 2022-04-19 15:30:29 +02:00
Marcelo Zimbres
bb28f34ecc Adds adapter for double. 2022-04-17 19:40:42 +02:00
Marcelo Zimbres
aa6251b96c Improves organization of examples. 2022-04-17 19:22:32 +02:00
Marcelo Zimbres
b12140ae8a Adds generic error codes. 2022-04-17 13:09:10 +02:00
Marcelo Zimbres
d845434869 Adds config object to client class. 2022-04-16 20:27:28 +02:00
Marcelo Zimbres
5e86fb9d03 Adds the size of the read to on_read. 2022-04-16 19:59:06 +02:00
Marcelo Zimbres
dc80c04347 Adds timeout to read operations. 2022-04-16 19:51:43 +02:00
Marcelo Zimbres
20d6c67f3d Fix error handling. 2022-04-16 16:47:32 +02:00
Marcelo Zimbres
8c4816fc8d Adds a timer to the write op in the high level client. 2022-04-16 14:14:55 +02:00
Marcelo Zimbres
18aa9ff726 Adds a timeout to the connect. 2022-04-16 12:51:00 +02:00
Marcelo Zimbres
b68e413351 Simplifications. 2022-04-15 18:26:07 +02:00
Marcelo Zimbres
27b3bb89fb Some improvements in the documentation. 2022-04-10 19:24:21 +02:00
Marcelo Zimbres
70cd9b0ffd Updates link to the RESP3 spec. 2022-04-10 12:59:37 +02:00
Marcelo Zimbres
43a08e834c Adds serialization example. 2022-04-10 11:57:30 +02:00
Marcelo Zimbres
19f03e2f41 Changes version. 2022-04-08 12:45:35 +02:00
Marcelo Zimbres
0fff6496fb Improvements in the documentation. 2022-04-08 12:44:45 +02:00
Marcelo Zimbres
57ba376544 Improvements in the documentation. 2022-04-07 23:35:13 +02:00
Marcelo Zimbres
46b86c20e5 Fixes documentation and some examples. 2022-04-07 21:08:03 +02:00
Marcelo Zimbres
577c6d35fb Changes how receivers work. 2022-04-07 16:14:29 +02:00
Marcelo Zimbres
42ef8a3b06 Change signature of adapters. 2022-04-07 09:32:10 +02:00
Marcelo Zimbres
f9af8e585b Unifies the array adapters. 2022-04-07 08:45:24 +02:00
Marcelo Zimbres
350ae19936 Removes typedef. 2022-04-06 20:23:13 +02:00
Marcelo Zimbres
57f2644903 Fixes the build. 2022-04-06 17:25:24 +02:00
Marcelo Zimbres
0ac0c3cf23 Improvements in the docs. 2022-04-05 23:33:20 +02:00
Marcelo Zimbres
5c23299a8a Ports to C++14. 2022-04-05 19:47:20 +02:00
Marcelo Zimbres
379da7a340 Some code improvements. 2022-04-04 22:38:02 +02:00
Marcelo Zimbres
bcf13c03c0 Docs. 2022-04-03 21:42:01 +02:00
Marcelo Zimbres
be79f58808 Simplifies the serializer. 2022-04-03 20:54:39 +02:00
Marcelo Zimbres
2c96b07623 More fixes in the docs. 2022-04-03 12:26:28 +02:00
Marcelo Zimbres
577d32f0e2 Adds low-level adapter examplea and fixes example exe names. 2022-04-02 22:16:41 +02:00
Marcelo Zimbres
5f93257502 One more example. 2022-04-02 16:30:32 +02:00
Marcelo Zimbres
409ee61522 Make the low_level tests independent of coroutine support. 2022-04-02 16:17:17 +02:00
Marcelo Zimbres
5aa034d6a5 Some improvements. 2022-03-30 21:50:28 +02:00
Marcelo Zimbres
0ff0e537ce Improvements. 2022-03-28 23:03:22 +02:00
Marcelo Zimbres
320ee6b3cc Fixes adapter for general aggregates. 2022-03-27 21:13:03 +02:00
Marcelo Zimbres
5061e5a7a6 More improvements in the tests. 2022-03-27 11:28:33 +02:00
Marcelo Zimbres
0bda78dd9c Some refactoring and improvements in the docs. 2022-03-26 21:28:56 +01:00
Marcelo Zimbres
8704e7756f More improvements in the tests. 2022-03-20 21:18:59 +01:00
Marcelo Zimbres
8207ef7e8a Improvements in the tests. 2022-03-20 12:02:42 +01:00
Marcelo Zimbres
4712872daf Many improvements. 2022-03-19 20:55:58 +01:00
Marcelo Zimbres
06752ac664 Adds low level api example. 2022-03-13 20:56:56 +01:00
Marcelo Zimbres
b3be596f77 Moves files around. 2022-03-13 20:43:13 +01:00
Marcelo Zimbres
f92290be16 Improvements in the docs. 2022-03-13 11:59:47 +01:00
Marcelo Zimbres
331010c240 More documentation. 2022-03-12 21:58:28 +01:00
Marcelo Zimbres
11b697c572 Progresses with docs. 2022-03-12 11:08:23 +01:00
Marcelo Zimbres
1218b2ef01 Adds on_push to the receiver. 2022-03-06 21:40:57 +01:00
Marcelo Zimbres
8cc142d55b Many improvements in the docs. 2022-03-06 11:34:59 +01:00
Marcelo Zimbres
0723922ac1 Fixes many bugs in the adapters. 2022-03-04 23:21:02 +01:00
Marcelo Zimbres
54ba34c70c More progresses with examples. 2022-03-02 22:10:57 +01:00
Marcelo Zimbres
efe44fbd45 Simplifies the receiver. 2022-02-28 22:03:51 +01:00
Marcelo Zimbres
268b59afbc More progress. 2022-02-27 21:06:43 +01:00
Marcelo Zimbres
6b6272694c More improvements in the client. 2022-02-26 23:07:34 +01:00
Marcelo Zimbres
f36f3b7b5e Improvements in the code. 2022-02-26 11:26:55 +01:00
Marcelo Zimbres
b4fef73b87 More improvements in the examples. 2022-02-23 23:12:42 +01:00
Marcelo Zimbres
c27b3b6b85 Some improvements in the code. 2022-02-22 21:44:37 +01:00
Marcelo Zimbres
feb383d7e2 Fixes some examples. 2022-02-20 20:00:56 +01:00
Marcelo Zimbres
ee71ff885d Adds single async_run function. 2022-02-20 11:19:34 +01:00
Marcelo Zimbres
80a80f44ff Progresses with the high level api. 2022-02-19 23:05:49 +01:00
Marcelo Zimbres
053ce3aea9 Adds async_run function. 2022-02-19 19:48:17 +01:00
Marcelo Zimbres
db27e3cc60 Improves intro example. 2022-02-19 17:13:02 +01:00
Marcelo Zimbres
449ba0dd73 More improvements. 2022-02-19 13:35:49 +01:00
Marcelo Zimbres
8728f58981 Improvements in the client class. 2022-02-19 13:05:25 +01:00
Marcelo Zimbres
2573f39aa1 More improvements in the client. 2022-02-13 16:20:03 +01:00
Marcelo Zimbres
f460bf43a5 Using vector instead of queue. 2022-02-13 09:16:39 +01:00
Marcelo Zimbres
85d0a30dad Improvements in the client.
- Implements the writer with lightwait coroutines.
2022-02-12 19:58:31 +01:00
Marcelo Zimbres
b7f22d8c61 More improvements in the examples. 2022-02-12 16:31:36 +01:00
Marcelo Zimbres
b04cc12d64 Lots of improvements. 2022-02-10 22:05:40 +01:00
Marcelo Zimbres
317690ee91 Missing files. 2022-02-07 09:31:41 +01:00
Marcelo Zimbres
fe3cc2efb0 Fixes doc with release path. 2022-02-05 22:28:34 +01:00
73 changed files with 6929 additions and 4787 deletions

View File

@@ -1 +1 @@
See file:///tmp/aedis/html/installation.html
See https://mzimbres.github.io/aedis/#using-aedis

373
LICENSE
View File

@@ -1,373 +0,0 @@
Mozilla Public License Version 2.0
==================================
1. Definitions
--------------
1.1. "Contributor"
means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
--------------------------------
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.

23
LICENSE.txt Normal file
View File

@@ -0,0 +1,23 @@
Boost Software License - Version 1.0 - August 17th, 2003
Permission is hereby granted, free of charge, to any person or organization
obtaining a copy of the software and accompanying documentation covered by
this license (the "Software") to use, reproduce, display, distribute,
execute, and transmit the Software, and to prepare derivative works of the
Software, and to permit third-parties to whom the Software is furnished to
do so, all subject to the following:
The copyright notices in the Software and this entire statement, including
the above license grant, this restriction and the following disclaimer,
must be included in all copies of the Software, in whole or in part, and
all derivative works of the Software, unless such copies or derivative
works are solely in the form of machine-executable object code generated by
a source language processor.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

View File

@@ -6,30 +6,31 @@ DISTCHECK_CONFIGURE_FLAGS = CPPFLAGS="$(BOOST_CPPFLAGS) $(CPPFLAGS)" LDFLAGS="$(
AM_CPPFLAGS =
AM_CPPFLAGS += $(BOOST_CPPFLAGS)
#AM_CPPFLAGS += -I$(top_srcdir)/include
AM_LDFLAGS =
AM_LDFLAGS += -pthread
check_PROGRAMS =
check_PROGRAMS += intro
check_PROGRAMS += sets
check_PROGRAMS += hashes
check_PROGRAMS += serialization
check_PROGRAMS += multipurpose_response
check_PROGRAMS += lists
check_PROGRAMS += key_expiration
check_PROGRAMS += response_adapter
check_PROGRAMS += sync
check_PROGRAMS += multipurpose_client
check_PROGRAMS += test_offline
check_PROGRAMS += test_online
check_PROGRAMS += intro_sync
check_PROGRAMS += serialization_sync
check_PROGRAMS += intro_high_level
check_PROGRAMS += aggregates_high_level
check_PROGRAMS += test_low_level
check_PROGRAMS += test_high_level
if HAVE_CXX20
check_PROGRAMS += transaction
check_PROGRAMS += custom_adapter
endif
EXTRA_PROGRAMS =
EXTRA_PROGRAMS += subscriber_high_level
EXTRA_PROGRAMS += commands
if HAVE_CXX20
EXTRA_PROGRAMS += subscriber
EXTRA_PROGRAMS += echo_server
EXTRA_PROGRAMS += chat_room
EXTRA_PROGRAMS += commands
endif
CLEANFILES =
CLEANFILES += $(EXTRA_PROGRAMS)
@@ -37,55 +38,55 @@ CLEANFILES += $(EXTRA_PROGRAMS)
.PHONY: all
all: $(check_PROGRAMS) $(EXTRA_PROGRAMS)
intro_SOURCES = $(top_srcdir)/examples/intro.cpp
sets_SOURCES = $(top_srcdir)/examples/sets.cpp
hashes_SOURCES = $(top_srcdir)/examples/hashes.cpp
serialization_SOURCES = $(top_srcdir)/examples/serialization.cpp
multipurpose_response_SOURCES = $(top_srcdir)/examples/multipurpose_response.cpp
lists_SOURCES = $(top_srcdir)/examples/lists.cpp
key_expiration_SOURCES = $(top_srcdir)/examples/key_expiration.cpp
response_adapter_SOURCES = $(top_srcdir)/examples/response_adapter.cpp
sync_SOURCES = $(top_srcdir)/examples/sync.cpp
multipurpose_client_SOURCES = $(top_srcdir)/examples/multipurpose_client.cpp
intro_high_level_SOURCES = $(top_srcdir)/examples/intro_high_level.cpp
aggregates_high_level_SOURCES = $(top_srcdir)/examples/aggregates_high_level.cpp
intro_sync_SOURCES = $(top_srcdir)/examples/intro_sync.cpp
serialization_sync_SOURCES = $(top_srcdir)/examples/serialization_sync.cpp
commands_SOURCES = $(top_srcdir)/tools/commands.cpp
test_offline_SOURCES = $(top_srcdir)/tests/offline.cpp
test_online_SOURCES = $(top_srcdir)/tests/online.cpp
subscriber_high_level_SOURCES = $(top_srcdir)/examples/subscriber_high_level.cpp
test_low_level_SOURCES = $(top_srcdir)/tests/low_level.cpp
test_high_level_SOURCES = $(top_srcdir)/tests/high_level.cpp
if HAVE_CXX20
transaction_SOURCES = $(top_srcdir)/examples/transaction.cpp
subscriber_SOURCES = $(top_srcdir)/examples/subscriber.cpp
custom_adapter_SOURCES = $(top_srcdir)/examples/custom_adapter.cpp
echo_server_SOURCES = $(top_srcdir)/examples/echo_server.cpp
chat_room_SOURCES = $(top_srcdir)/examples/chat_room.cpp
transaction_SOURCES = $(top_srcdir)/examples/transaction.cpp
endif
nobase_include_HEADERS =\
$(top_srcdir)/aedis/config.hpp\
$(top_srcdir)/aedis/src.hpp\
$(top_srcdir)/aedis/redis/command.hpp\
$(top_srcdir)/aedis/generic/client.hpp\
$(top_srcdir)/aedis/generic/serializer.hpp\
$(top_srcdir)/aedis/generic/error.hpp\
$(top_srcdir)/aedis/generic/impl/error.ipp\
$(top_srcdir)/aedis/generic/detail/client_ops.hpp\
$(top_srcdir)/aedis/sentinel/command.hpp\
$(top_srcdir)/aedis/aedis.hpp\
$(top_srcdir)/aedis/resp3/adapter/detail/adapters.hpp\
$(top_srcdir)/aedis/resp3/adapter/error.hpp\
$(top_srcdir)/aedis/resp3/adapt.hpp\
$(top_srcdir)/aedis/resp3/detail/composer.hpp\
$(top_srcdir)/aedis/adapter/detail/adapters.hpp\
$(top_srcdir)/aedis/adapter/error.hpp\
$(top_srcdir)/aedis/adapter/impl/error.ipp\
$(top_srcdir)/aedis/adapter/adapt.hpp\
$(top_srcdir)/aedis/adapter/detail/response_traits.hpp\
$(top_srcdir)/aedis/resp3/node.hpp\
$(top_srcdir)/aedis/resp3/compose.hpp\
$(top_srcdir)/aedis/resp3/detail/read_ops.hpp\
$(top_srcdir)/aedis/resp3/detail/parser.hpp\
$(top_srcdir)/aedis/resp3/serializer.hpp\
$(top_srcdir)/aedis/resp3/response_traits.hpp\
$(top_srcdir)/aedis/resp3/node.hpp\
$(top_srcdir)/aedis/resp3/error.hpp\
$(top_srcdir)/aedis/resp3/impl/error.ipp\
$(top_srcdir)/aedis/resp3/type.hpp\
$(top_srcdir)/aedis/resp3/read.hpp\
$(top_srcdir)/aedis/redis/impl/command.ipp\
$(top_srcdir)/aedis/sentinel/impl/command.ipp\
$(top_srcdir)/aedis/resp3/detail/impl/parser.ipp\
$(top_srcdir)/aedis/resp3/impl/type.ipp\
$(top_srcdir)/aedis/resp3/impl/node.ipp\
$(top_srcdir)/aedis/redis/experimental/client.hpp\
$(top_srcdir)/aedis/redis/experimental/impl/client.ipp
$(top_srcdir)/aedis/resp3/impl/type.ipp
nobase_noinst_HEADERS =\
$(top_srcdir)/examples/lib/net_utils.hpp\
$(top_srcdir)/examples/lib/user_session.hpp\
$(top_srcdir)/tests/check.hpp\
$(top_srcdir)/tests/test_stream.hpp
$(top_srcdir)/examples/user_session.hpp\
$(top_srcdir)/examples/print.hpp\
$(top_srcdir)/examples/mystruct.hpp\
$(top_srcdir)/tests/check.hpp
TESTS = $(check_PROGRAMS)

82
aedis/adapter/adapt.hpp Normal file
View File

@@ -0,0 +1,82 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#ifndef AEDIS_ADAPTER_ADAPT_HPP
#define AEDIS_ADAPTER_ADAPT_HPP
#include <aedis/adapter/detail/response_traits.hpp>
namespace aedis {
namespace adapter {
template <class T>
using adapter_t = typename detail::adapter_t<T>;
/** \brief Creates a dummy response adapter.
\ingroup any
The adapter returned by this function ignores responses. It is
useful to avoid wasting time with responses which are not needed.
Example:
@code
// Pushes and writes some commands to the server.
sr.push(command::hello, 3);
sr.push(command::ping);
sr.push(command::quit);
net::write(socket, net::buffer(request));
// Ignores all responses except for the response to ping.
std::string buffer;
resp3::read(socket, dynamic_buffer(buffer), adapt()); // hello
resp3::read(socket, dynamic_buffer(buffer), adapt(resp)); // ping
resp3::read(socket, dynamic_buffer(buffer, adapt())); // quit
@endcode
*/
inline
auto adapt() noexcept
{ return detail::response_traits<void>::adapt(); }
/** \brief Adapts user data to read operations.
* \ingroup any
*
* STL containers, \c std::tuple and built-in types are supported and
* can be used in conjunction with \c boost::optional<T>.
*
* Example usage:
*
* @code
* std::unordered_map<std::string, std::string> cont;
* co_await async_read(socket, buffer, adapt(cont));
* @endcode
*
* For a transaction
*
* @code
* sr.push(command::multi);
* sr.push(command::ping, ...);
* sr.push(command::incr, ...);
* sr.push_range(command::rpush, ...);
* sr.push(command::lrange, ...);
* sr.push(command::incr, ...);
* sr.push(command::exec);
*
* co_await async_write(socket, buffer(request));
*
* // Reads the response to a transaction
* std::tuple<std::string, int, int, std::vector<std::string>, int> execs;
* co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(execs));
* @endcode
*/
template<class T>
auto adapt(T& t) noexcept
{ return detail::response_traits<T>::adapt(t); }
} // adapter
} // aedis
#endif // AEDIS_ADAPTER_ADAPT_HPP

View File

@@ -0,0 +1,428 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#ifndef AEDIS_ADAPTER_ADAPTERS_HPP
#define AEDIS_ADAPTER_ADAPTERS_HPP
#include <set>
#include <unordered_set>
#include <forward_list>
#include <system_error>
#include <map>
#include <unordered_map>
#include <list>
#include <deque>
#include <vector>
#include <array>
#include <boost/assert.hpp>
#include <boost/optional.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/home/x3.hpp>
#include <boost/utility/string_view.hpp>
#include <aedis/resp3/type.hpp>
#include <aedis/resp3/detail/parser.hpp>
#include <aedis/generic/serializer.hpp>
#include <aedis/resp3/node.hpp>
#include <aedis/adapter/error.hpp>
namespace aedis {
namespace adapter {
namespace detail {
double
parse_double(
char const* data,
std::size_t size,
boost::system::error_code& ec)
{
static constexpr boost::spirit::x3::real_parser<double> p{};
double ret;
if (!parse(data, data + size, p, ret))
ec = error::not_a_double;
return ret;
}
// Serialization.
template <class T>
typename std::enable_if<std::is_integral<T>::value, void>::type
from_string(
T& i,
boost::string_view sv,
boost::system::error_code& ec)
{
i = resp3::detail::parse_uint(sv.data(), sv.size(), ec);
}
void from_string(
bool& t,
boost::string_view sv,
boost::system::error_code& ec)
{
t = *sv.data() == 't';
}
void from_string(
double& d,
boost::string_view sv,
boost::system::error_code& ec)
{
d = parse_double(sv.data(), sv.size(), ec);
}
template <class CharT, class Traits, class Allocator>
void
from_string(
std::basic_string<CharT, Traits, Allocator>& s,
boost::string_view sv,
boost::system::error_code&)
{
s.append(sv.data(), sv.size());
}
//================================================
void set_on_resp3_error(resp3::type t, boost::system::error_code& ec)
{
switch (t) {
case resp3::type::simple_error: ec = adapter::error::simple_error; return;
case resp3::type::blob_error: ec = adapter::error::blob_error; return;
case resp3::type::null: ec = adapter::error::null; return;
default: return;
}
}
template <class Result>
class general_aggregate {
private:
Result* result_;
public:
general_aggregate(Result* c = nullptr): result_(c) {}
void operator()(resp3::node<boost::string_view> const& n, boost::system::error_code&)
{
result_->push_back({n.data_type, n.aggregate_size, n.depth, std::string{std::cbegin(n.value), std::cend(n.value)}});
}
};
template <class Node>
class general_simple {
private:
Node* result_;
public:
general_simple(Node* t = nullptr) : result_(t) {}
void operator()(resp3::node<boost::string_view> const& n, boost::system::error_code&)
{
result_->data_type = n.data_type;
result_->aggregate_size = n.aggregate_size;
result_->depth = n.depth;
result_->value.assign(n.value.data(), n.value.size());
}
};
template <class Result>
class simple_impl {
public:
void on_value_available(Result&) {}
void
operator()(
Result& result,
resp3::node<boost::string_view> const& n,
boost::system::error_code& ec)
{
set_on_resp3_error(n.data_type, ec);
if (ec)
return;
if (is_aggregate(n.data_type)) {
ec = adapter::error::expects_simple_type;
return;
}
from_string(result, n.value, ec);
}
};
template <class Result>
class set_impl {
private:
typename Result::iterator hint_;
public:
void on_value_available(Result& result)
{ hint_ = std::end(result); }
void
operator()(
Result& result,
resp3::node<boost::string_view> const& nd,
boost::system::error_code& ec)
{
set_on_resp3_error(nd.data_type, ec);
if (ec)
return;
if (is_aggregate(nd.data_type)) {
if (nd.data_type != resp3::type::set)
ec = error::expects_set_type;
return;
}
BOOST_ASSERT(nd.aggregate_size == 1);
if (nd.depth < 1) {
ec = adapter::error::expects_set_type;
return;
}
typename Result::key_type obj;
from_string(obj, nd.value, ec);
hint_ = result.insert(hint_, std::move(obj));
}
};
template <class Result>
class map_impl {
private:
typename Result::iterator current_;
bool on_key_ = true;
public:
void on_value_available(Result& result)
{ current_ = std::end(result); }
void
operator()(
Result& result,
resp3::node<boost::string_view> const& nd,
boost::system::error_code& ec)
{
set_on_resp3_error(nd.data_type, ec);
if (ec)
return;
if (is_aggregate(nd.data_type)) {
if (element_multiplicity(nd.data_type) != 2)
ec = error::expects_map_type;
return;
}
BOOST_ASSERT(nd.aggregate_size == 1);
if (nd.depth < 1) {
ec = adapter::error::expects_map_type;
return;
}
if (on_key_) {
typename Result::key_type obj;
from_string(obj, nd.value, ec);
current_ = result.insert(current_, {std::move(obj), {}});
} else {
typename Result::mapped_type obj;
from_string(obj, nd.value, ec);
current_->second = std::move(obj);
}
on_key_ = !on_key_;
}
};
template <class Result>
class vector_impl {
public:
void on_value_available(Result& ) { }
void
operator()(
Result& result,
resp3::node<boost::string_view> const& nd,
boost::system::error_code& ec)
{
set_on_resp3_error(nd.data_type, ec);
if (ec)
return;
if (is_aggregate(nd.data_type)) {
auto const m = element_multiplicity(nd.data_type);
result.reserve(result.size() + m * nd.aggregate_size);
} else {
result.push_back({});
from_string(result.back(), nd.value, ec);
}
}
};
template <class Result>
class array_impl {
private:
int i_ = -1;
public:
void on_value_available(Result& ) { }
void
operator()(
Result& result,
resp3::node<boost::string_view> const& nd,
boost::system::error_code& ec)
{
set_on_resp3_error(nd.data_type, ec);
if (ec)
return;
if (is_aggregate(nd.data_type)) {
if (i_ != -1) {
ec = adapter::error::nested_aggregate_unsupported;
return;
}
if (result.size() != nd.aggregate_size * element_multiplicity(nd.data_type)) {
ec = error::incompatible_size;
return;
}
} else {
if (i_ == -1) {
ec = adapter::error::expects_aggregate_type;
return;
}
BOOST_ASSERT(nd.aggregate_size == 1);
from_string(result.at(i_), nd.value, ec);
}
++i_;
}
};
template <class Result>
struct list_impl {
void on_value_available(Result& ) { }
void
operator()(
Result& result,
resp3::node<boost::string_view> const& nd,
boost::system::error_code& ec)
{
set_on_resp3_error(nd.data_type, ec);
if (ec)
return;
if (!is_aggregate(nd.data_type)) {
BOOST_ASSERT(nd.aggregate_size == 1);
if (nd.depth < 1) {
ec = adapter::error::expects_aggregate_type;
return;
}
result.push_back({});
from_string(result.back(), nd.value, ec);
}
}
};
//---------------------------------------------------
template <class T>
struct impl_map { using type = simple_impl<T>; };
template <class Key, class Compare, class Allocator>
struct impl_map<std::set<Key, Compare, Allocator>> { using type = set_impl<std::set<Key, Compare, Allocator>>; };
template <class Key, class Compare, class Allocator>
struct impl_map<std::multiset<Key, Compare, Allocator>> { using type = set_impl<std::multiset<Key, Compare, Allocator>>; };
template <class Key, class Hash, class KeyEqual, class Allocator>
struct impl_map<std::unordered_set<Key, Hash, KeyEqual, Allocator>> { using type = set_impl<std::unordered_set<Key, Hash, KeyEqual, Allocator>>; };
template <class Key, class Hash, class KeyEqual, class Allocator>
struct impl_map<std::unordered_multiset<Key, Hash, KeyEqual, Allocator>> { using type = set_impl<std::unordered_multiset<Key, Hash, KeyEqual, Allocator>>; };
template <class Key, class T, class Compare, class Allocator>
struct impl_map<std::map<Key, T, Compare, Allocator>> { using type = map_impl<std::map<Key, T, Compare, Allocator>>; };
template <class Key, class T, class Compare, class Allocator>
struct impl_map<std::multimap<Key, T, Compare, Allocator>> { using type = map_impl<std::multimap<Key, T, Compare, Allocator>>; };
template <class Key, class Hash, class KeyEqual, class Allocator>
struct impl_map<std::unordered_map<Key, Hash, KeyEqual, Allocator>> { using type = map_impl<std::unordered_map<Key, Hash, KeyEqual, Allocator>>; };
template <class Key, class Hash, class KeyEqual, class Allocator>
struct impl_map<std::unordered_multimap<Key, Hash, KeyEqual, Allocator>> { using type = map_impl<std::unordered_multimap<Key, Hash, KeyEqual, Allocator>>; };
template <class T, class Allocator>
struct impl_map<std::vector<T, Allocator>> { using type = vector_impl<std::vector<T, Allocator>>; };
template <class T, std::size_t N>
struct impl_map<std::array<T, N>> { using type = array_impl<std::array<T, N>>; };
template <class T, class Allocator>
struct impl_map<std::list<T, Allocator>> { using type = list_impl<std::list<T, Allocator>>; };
template <class T, class Allocator>
struct impl_map<std::deque<T, Allocator>> { using type = list_impl<std::deque<T, Allocator>>; };
//---------------------------------------------------
template <class Result>
class wrapper {
private:
Result* result_;
typename impl_map<Result>::type impl_;
public:
wrapper(Result* t = nullptr) : result_(t)
{ impl_.on_value_available(*result_); }
void
operator()(
resp3::node<boost::string_view> const& nd,
boost::system::error_code& ec)
{
BOOST_ASSERT(result_);
impl_(*result_, nd, ec);
}
};
template <class T>
class wrapper<boost::optional<T>> {
private:
boost::optional<T>* result_;
typename impl_map<T>::type impl_;
public:
wrapper(boost::optional<T>* o = nullptr) : result_(o), impl_{} {}
void
operator()(
resp3::node<boost::string_view> const& nd,
boost::system::error_code& ec)
{
if (nd.data_type == resp3::type::null)
return;
if (!result_->has_value()) {
*result_ = T{};
impl_.on_value_available(result_->value());
}
impl_(result_->value(), nd, ec);
}
};
} // detail
} // adapter
} // aedis
#endif // AEDIS_ADAPTER_ADAPTERS_HPP

View File

@@ -0,0 +1,161 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#ifndef AEDIS_ADAPTER_RESPONSE_TRAITS_HPP
#define AEDIS_ADAPTER_RESPONSE_TRAITS_HPP
#include <vector>
#include <tuple>
#include <boost/mp11.hpp>
#include <boost/variant2.hpp>
#include <aedis/resp3/type.hpp>
#include <aedis/resp3/read.hpp>
#include <aedis/adapter/detail/adapters.hpp>
#include <aedis/adapter/error.hpp>
namespace aedis {
namespace adapter {
namespace detail {
/* Traits class for response objects.
*
* Provides traits for all supported response types i.e. all STL
* containers and C++ buil-in types.
*/
template <class ResponseType>
struct response_traits
{
using adapter_type = adapter::detail::wrapper<ResponseType>;
static auto adapt(ResponseType& r) noexcept { return adapter_type{&r}; }
};
template <class T>
using adapter_t = typename response_traits<T>::adapter_type;
template <class T>
struct response_traits<resp3::node<T>>
{
using response_type = resp3::node<T>;
using adapter_type = adapter::detail::general_simple<response_type>;
static auto adapt(response_type& v) noexcept { return adapter_type{&v}; }
};
template <class String, class Allocator>
struct response_traits<std::vector<resp3::node<String>, Allocator>>
{
using response_type = std::vector<resp3::node<String>, Allocator>;
using adapter_type = adapter::detail::general_aggregate<response_type>;
static auto adapt(response_type& v) noexcept { return adapter_type{&v}; }
};
template <>
struct response_traits<void>
{
using response_type = void;
using adapter_type = resp3::detail::ignore_response;
static auto adapt() noexcept { return adapter_type{}; }
};
namespace detail {
// Duplicated here to avoid circular include dependency.
template<class T>
auto internal_adapt(T& t) noexcept
{ return response_traits<T>::adapt(t); }
template <std::size_t N>
struct assigner {
template <class T1, class T2>
static void assign(T1& dest, T2& from)
{
dest[N] = internal_adapt(std::get<N>(from));
assigner<N - 1>::assign(dest, from);
}
};
template <>
struct assigner<0> {
template <class T1, class T2>
static void assign(T1& dest, T2& from)
{
dest[0] = internal_adapt(std::get<0>(from));
}
};
template <class Tuple>
class static_aggregate_adapter {
private:
using adapters_array_type =
std::array<
boost::mp11::mp_unique<
boost::mp11::mp_rename<
boost::mp11::mp_transform<
adapter_t, Tuple>,
boost::variant2::variant>>,
std::tuple_size<Tuple>::value>;
std::size_t i_ = 0;
std::size_t aggregate_size_ = 0;
adapters_array_type adapters_;
public:
static_aggregate_adapter(Tuple* r = nullptr)
{
detail::assigner<std::tuple_size<Tuple>::value - 1>::assign(adapters_, *r);
}
void count(resp3::node<boost::string_view> const& nd)
{
if (nd.depth == 1) {
if (is_aggregate(nd.data_type))
aggregate_size_ = element_multiplicity(nd.data_type) * nd.aggregate_size;
else
++i_;
return;
}
if (--aggregate_size_ == 0)
++i_;
}
void
operator()(
resp3::node<boost::string_view> const& nd,
boost::system::error_code& ec)
{
using boost::variant2::visit;
if (nd.depth == 0) {
auto const real_aggr_size = nd.aggregate_size * element_multiplicity(nd.data_type);
if (real_aggr_size != std::tuple_size<Tuple>::value)
ec = error::incompatible_size;
return;
}
visit([&](auto& arg){arg(nd, ec);}, adapters_[i_]);
count(nd);
}
};
} // detail
template <class... Ts>
struct response_traits<std::tuple<Ts...>>
{
using response_type = std::tuple<Ts...>;
using adapter_type = detail::static_aggregate_adapter<response_type>;
static auto adapt(response_type& r) noexcept { return adapter_type{&r}; }
};
} // detail
} // adapter
} // aedis
#endif // AEDIS_ADAPTER_RESPONSE_TRAITS_HPP

66
aedis/adapter/error.hpp Normal file
View File

@@ -0,0 +1,66 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#ifndef AEDIS_ADAPTER_ERROR_HPP
#define AEDIS_ADAPTER_ERROR_HPP
#include <system_error>
namespace aedis {
namespace adapter {
/** \brief Adapter errors.
* \ingroup any
*/
enum class error
{
/// Expects a simple RESP3 type but got an aggregate.
expects_simple_type = 1,
/// Expects aggregate type.
expects_aggregate_type,
/// Expects a map but got other aggregate.
expects_map_type,
/// Expects a set aggregate but got something else.
expects_set_type,
/// Nested response not supported.
nested_aggregate_unsupported,
/// Got RESP3 simple error.
simple_error,
/// Got RESP3 blob_error.
blob_error,
/// Aggregate container has incompatible size.
incompatible_size,
/// Not a double
not_a_double,
/// Got RESP3 null type.
null
};
/** \brief Creates a error_code object from an error.
* \ingroup any
*/
boost::system::error_code make_error_code(error e);
} // adapter
} // aedis
namespace std {
template<>
struct is_error_code_enum<::aedis::adapter::error> : std::true_type {};
} // std
#endif // AEDIS_ADAPTER_ERROR_HPP

View File

@@ -0,0 +1,53 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#include <system_error>
#include <boost/assert.hpp>
namespace aedis {
namespace adapter {
namespace detail {
struct error_category_impl : boost::system::error_category {
char const* name() const noexcept override
{
return "aedis.adapter";
}
std::string message(int ev) const override
{
switch(static_cast<error>(ev)) {
case error::expects_simple_type: return "Expects a simple RESP3 type.";
case error::expects_aggregate_type: return "Expects aggregate type.";
case error::expects_map_type: return "Expects map type.";
case error::expects_set_type: return "Expects set type.";
case error::nested_aggregate_unsupported: return "Nested aggregate unsupported.";
case error::simple_error: return "Got RESP3 simple-error.";
case error::blob_error: return "Got RESP3 blob-error.";
case error::incompatible_size: return "Aggregate container has incompatible size.";
case error::not_a_double: return "Not a double.";
case error::null: return "Got RESP3 null.";
default: BOOST_ASSERT(false);
}
}
};
boost::system::error_category const& category()
{
static error_category_impl instance;
return instance;
}
} // detail
boost::system::error_code make_error_code(error e)
{
return boost::system::error_code{static_cast<int>(e), detail::category()};
}
} // adapter
} // aedis

View File

@@ -1,97 +1,583 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#pragma once
#ifndef AEDIS_HPP
#define AEDIS_HPP
#include <aedis/config.hpp>
#include <aedis/resp3/read.hpp>
#include <aedis/adapter/adapt.hpp>
#include <aedis/adapter/error.hpp>
#include <aedis/redis/command.hpp>
#include <aedis/sentinel/command.hpp>
#include <aedis/resp3/type.hpp>
#include <aedis/resp3/read.hpp>
#include <aedis/resp3/adapt.hpp>
#include <aedis/resp3/error.hpp>
#include <aedis/resp3/serializer.hpp>
#include <aedis/resp3/response_traits.hpp>
#include <aedis/redis/experimental/client.hpp>
#include <aedis/generic/error.hpp>
#include <aedis/generic/client.hpp>
#include <aedis/generic/serializer.hpp>
/** \mainpage Documentation
\tableofcontents
\section Overview
Aedis is low-level redis client library built on top of Boost.Asio
that implements communication with a Redis server over its native
protocol RESP3. It has first-class support for STL containers and
C++ built in types among other things. You will be able to
implement your own redis client or use a general purpose provided
by the library. For more information about Redis see
https://redis.io/
Aedis is a [Redis](https://redis.io/) client library built on top
of [Asio](https://www.boost.org/doc/libs/release/doc/html/boost_asio.html)
that provides simple and efficient communication with a Redis
server. Some of its distinctive features are
@li Support for the latest version of the Redis communication protocol [RESP3](https://github.com/redis/redis-specifications/blob/master/protocol/RESP3.md).
@li First class support for STL containers and C++ built-in types.
@li Serialization and deserialization of your own data types that avoid unnecessary copies.
@li Support for Redis [sentinel](https://redis.io/docs/manual/sentinel).
@li Sync and async API.
In addition to that, Aedis provides a high-level client that offers the following functionality
@li Management of message queues.
@li Simplified handling of server pushes.
@li Zero asymptotic allocations by means of memory reuse.
@li Healthy checks.
If you are interested in a detailed comparison of Redis clients
and the design rationale behind Aedis jump to \ref why-aedis. Now
let us have a look at the low-level API.
\section low-level-api Low-level API
The low-level API is very useful for tasks that can be performed
in short lived connections, for example, assume we want to perform
the following steps
@li Set the value of a Redis key.
@li Set the expiration of that key to two seconds.
@li Get and return its old value.
@li Quit
The coroutine-based asynchronous implementation of the steps above look like
@code
net::awaitable<std::string> set(net::ip::tcp::endpoint ep)
{
// To make code less verbose
using tcp_socket = net::use_awaitable_t<>::as_default_on_t<net::ip::tcp::socket>;
tcp_socket socket{co_await net::this_coro::executor};
co_await socket.async_connect(ep);
std::string buffer, response;
auto sr = make_serializer(request);
sr.push(command::hello, 3);
sr.push(command::set, "key", "Value", "EX", "2", "get");
sr.push(command::quit);
co_await net::async_write(socket, net::buffer(buffer));
buffer.clear();
auto dbuffer = net::dynamic_buffer(read_buffer);
co_await resp3::async_read(socket, dbuffer); // Hello ignored.
co_await resp3::async_read(socket, dbuffer, adapt(response)); // Set
co_await resp3::async_read(socket, dbuffer); // Quit ignored.
co_return response;
}
@endcode
The simplicity of the code above makes it self explanatory
@li Connect to the Redis server.
@li Declare a \c std::string to hold the request and add some commands in it with a serializer.
@li Write the payload to the socket and read the responses in the same order they were sent.
@li Return the response to the user.
The @c hello command above is always required and must be sent
first as it informs we want to communicate over RESP3.
\subsection requests Requests
As stated above, requests are created by defining a storage object
and a serializer that knows how to convert user data into valid
RESP3 wire-format. They are composed of one or more
commands (in Redis documentation they are called [pipelines](https://redis.io/topics/pipelining)),
which means users can add
as many commands to the request as they like, a feature that aids
performance.
The individual commands in a request assume many
different forms
@li With and without keys.
@li Variable length arguments.
@li Ranges.
@li etc.
To account for all these variations, the \c serializer class
offers some member functions, each of them with a couple of
overloads, for example
@code
// Some data to send to Redis.
std::string value = "some value";
std::list<std::string> list {"channel1", "channel2", "channel3"};
std::map<std::string, mystruct> map
{ {"key1", "value1"}
, {"key2", "value2"}
, {"key3", "value3"}};
// Command with no arguments
sr.push(command::quit);
// Command with variable lenght arguments.
sr.push(command::set, "key", value, "EX", "2");
// Sends a container, no key.
sr.push_range(command::subscribe, list);
// Same as above but an iterator range.
sr.push_range2(command::subscribe, std::cbegin(list), std::cend(list));
// Sends a container, with key.
sr.push_range(command::hset, "key", map);
// Same as above but as iterator range.
sr.push_range2(command::hset, "key", std::cbegin(map), std::cend(map));
@endcode
Once all commands have been added to the request, we can write it
as usual by writing the payload to the socket
@code
co_await net::async_write(socket, buffer(request));
@endcode
\subsubsection requests-serialization Serialization
The \c send and \c send_range functions above work with integers
e.g. \c int and \c std::string out of the box. To send your own
data type defined the \c to_bulk function like this
@code
// Example struct.
struct mystruct {
// ...
};
void to_bulk(std::string& to, mystruct const& obj)
{
// Convert to obj string and call to_bulk (see also add_header
// and add_separator)
auto dummy = "Dummy serializaiton string.";
aedis::resp3::to_bulk(to, dummy);
}
std::map<std::string, mystruct> map
{ {"key1", {...}}
, {"key2", {...}}
, {"key3", {...}}};
db.send_range(command::hset, "key", map);
@endcode
It is quite common to store json string in Redis for example.
\subsection low-level-responses Responses
To read responses effectively, users must know their RESP3 type,
this can be found in the Redis documentation of each command
(https://redis.io/commands). For example
Command | RESP3 type | Documentation
---------|-------------------------------------|--------------
lpush | Number | https://redis.io/commands/lpush
lrange | Array | https://redis.io/commands/lrange
set | Simple-string, null or blob-string | https://redis.io/commands/set
get | Blob-string | https://redis.io/commands/get
smembers | Set | https://redis.io/commands/smembers
hgetall | Map | https://redis.io/commands/hgetall
Once the RESP3 type of a given response is known we can choose a
proper C++ data structure to receive it in. Fortunately, this is a
simple task for most types. The table below summarises the options
RESP3 type | C++ | Type
---------------|--------------------------------------------------------------|------------------
Simple-string | \c std::string | Simple
Simple-error | \c std::string | Simple
Blob-string | \c std::string, \c std::vector | Simple
Blob-error | \c std::string, \c std::vector | Simple
Number | `long long`, `int`, `std::size_t`, \c std::string | Simple
Double | `double`, \c std::string | Simple
Null | `boost::optional<T>` | Simple
Array | \c std::vector, \c std::list, \c std::array, \c std::deque | Aggregate
Map | \c std::vector, \c std::map, \c std::unordered_map | Aggregate
Set | \c std::vector, \c std::set, \c std::unordered_set | Aggregate
Push | \c std::vector, \c std::map, \c std::unordered_map | Aggregate
Responses that contain nested aggregates or heterogeneous data
types will be given special treatment later. As of this writing,
not all RESP3 types are used by the Redis server, which means in
practice users will be concerned with a reduced subset of the
RESP3 specification. Now let us see some examples
@code
auto dbuffer = dynamic_buffer(buffer);
// To ignore the response.
co_await resp3::async_read(socket, dbuffer, adapt());
// Read in a std::string e.g. get.
std::string str;
co_await resp3::async_read(socket, dbuffer, adapt(str));
// Read in a long long e.g. rpush.
long long number;
co_await resp3::async_read(socket, dbuffer, adapt(number));
// Read in a std::set e.g. smembers.
std::set<T, U> set;
co_await resp3::async_read(socket, dbuffer, adapt(set));
// Read in a std::map e.g. hgetall.
std::map<T, U> set;
co_await resp3::async_read(socket, dbuffer, adapt(map));
// Read in a std::unordered_map e.g. hgetall.
std::unordered_map<T, U> umap;
co_await resp3::async_read(socket, dbuffer, adapt(umap));
// Read in a std::vector e.g. lrange.
std::vector<T> vec;
co_await resp3::async_read(socket, dbuffer, adapt(vec));
@endcode
In other words, it is straightforward, just pass the result of \c
adapt to the read function and make sure the response data type is
compatible with the data structure you are calling @c adapter(...)
with. All standard C++ containers are supported by Aedis.
\subsubsection Optional
It is not uncommon for apps to access keys that do not exist or
that have already expired in the Redis server, to deal with these
cases Aedis provides support for \c boost::optional. To use it,
wrap your type around \c boost::optional like this
@code
boost::optional<std::unordered_map<T, U>> umap;
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(umap));
@endcode
Everything else stays the same, before accessing data, users will
have to check or assert the optional contains a value.
\subsubsection heterogeneous_aggregates Heterogeneous aggregates
There are cases where Redis returns aggregates that
contain heterogeneous data, for example, an array that contains
integers, strings nested sets etc. Aedis supports reading such
aggregates in a \c std::tuple efficiently as long as the they
don't contain 3-order nested aggregates e.g. an array that
contains an array of arrays. For example, to read the response to
a \c hello command we can use the following response type.
@code
using hello_type = std::tuple<
std::string, std::string,
std::string, std::string,
std::string, int,
std::string, int,
std::string, std::string,
std::string, std::string,
std::string, std::vector<std::string>>;
@endcode
Transactions are another example where this feature is useful, for
example, the response to the transaction below
@code
db.send(command::multi);
db.send(command::get, "key1");
db.send(command::lrange, "key2", 0, -1);
db.send(command::hgetall, "key3");
db.send(command::exec);
@endcode
can be read in the following way
@code
std::tuple<
boost::optional<std::string>, // Response to get
boost::optional<std::vector<std::string>>, // Response to lrange
boost::optional<std::map<std::string, std::string>> // Response to hgetall
> trans;
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // Ignore multi
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // Ignore get
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // Ignore lrange
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // Ignore hgetall
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(trans));
@endcode
Note that above we are not ignoring the response to the commands
themselves but whether they have been successfully queued. Only
after @c exec is received Redis will execute them in sequence and
send all responses together in an array.
\subsubsection Serialization
As mentioned in \ref requests-serialization, it is common for
users to serialized data before sending it to Redis e.g. json
strings, for example
@code
sr.push(command::set, "key", "{"Server": "Redis"}"); // Unquoted for readability.
sr.push(command::get, "key")
@endcode
For performance and convenience reasons, we may want to avoid
receiving the response to the \c get command above as a string
just to convert it later to a e.g. deserialized json. To support
this, Aedis calls a user defined \c from_string function while
parsing the response. In simple terms, define your type
@code
struct mystruct {
// struct fields.
};
@endcode
and deserialize it from a string in a function \c from_string with
the following signature
@code
void from_string(mystruct& obj, char const* p, std::size_t size, boost::system::error_code& ec)
{
// Deserializes p into obj.
}
@endcode
After that, you can start receiving data efficiently in the desired
types e.g. \c mystruct, \c std::map<std::string, mystruct> etc.
\subsubsection gen-case The general case
As already mentioned, there are cases where responses to Redis
commands won't fit in the model presented above, some examples are
@li Commands (like \c set) whose response don't have a fixed
RESP3 type. Expecting an \c int and receiving a blob-string
will result in error.
@li RESP3 aggregates that contain nested aggregates can't be read in STL containers.
@li Transactions with a dynamic number of commands can't be read in a \c std::tuple.
To deal with these cases Aedis provides the \c resp3::node
type, that is the most general form of an element in a response,
be it a simple RESP3 type or an aggregate. It is defined like this
@code
template <class String>
struct node {
// The RESP3 type of the data in this node.
type data_type;
// The number of elements of an aggregate (or 1 for simple data).
std::size_t aggregate_size;
// The depth of this node in the response tree.
std::size_t depth;
// The actual data. For aggregate types this is always empty.
String value;
};
@endcode
Any response to a Redis command can be received in a \c
std::vector<node<std::string>>. The vector can be seen as a
pre-order view of the response tree
(https://en.wikipedia.org/wiki/Tree_traversal#Pre-order,_NLR).
Using it is no different that using other types
@code
// Receives any RESP3 simple data type.
node<std::string> resp;
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(resp));
// Receives any RESP3 simple or aggregate data type.
std::vector<node<std::string>> resp;
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(resp));
@endcode
For example, suppose we want to retrieve a hash data structure
from Redis with \c hgetall, some of the options are
@li \c std::vector<node<std::string>: Works always.
@li \c std::vector<std::string>: Efficient and flat, all elements as string.
@li \c std::map<std::string, std::string>: Efficient if you need the data as a \c std::map
@li \c std::map<U, V>: Efficient if you are storing serialized data. Avoids temporaries and requires \c from_string for \c U and \c V.
In addition to the above users can also use unordered versions of the containers. The same reasoning also applies to sets e.g. \c smembers.
\subsubsection low-level-adapters Adapters
Users that are not satisfied with any of the options above can
write their own adapters very easily. For example, the adapter below
can be used to print incoming data to the screen.
@code
auto adapter = [](resp3::node<boost::string_view> const& nd, boost::system::error_code&)
{
std::cout << nd << std::endl;
};
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapter);
@endcode
See more in the \ref examples section.
\section high-level-api High-level API
As stated earlier, the low-level API is very useful for tasks that
can be performed with short lived connections. Sometimes however,
the need for long-lived connections becomes compeling
@li \b Server \b pushes: Short lived connections can't deal with server pushes, that means no [client side caching](https://redis.io/topics/client-side-caching), [notifications](https://redis.io/topics/notifications) and [pubsub](https://redis.io/topics/pubsub).
@li \b Performance: Keep opening and closing connections impact performance serverely.
@li \b Pipeline: Code such as shown in \ref low-level-api don't support pipelines well since it can only send a fixed number of commands at time. It misses important optimization opportunities (https://redis.io/topics/pipelining).
A serious implementation that supports the points listed above is
far from trivial and involves many complex asynchronous operations
@li \c async_resolve: Resolve a hostname.
@li \c async_connect: Connect to Redis.
@li \c async_read: Performed in a loop as long as the connection lives.
@li \c async_write: Performed everytime a new message is added.
@li \c async_wait: To timout all operations above if the server becomes unresponsive.
Notice that many of the operations above will run concurrently with each other and, in addition to that
@li \c async_write operations require management of the message queue to prevent concurrent writes.
@li Healthy checks must be sent periodically by the client to detect a dead or unresponsive server.
@li Recovery after a disconnection to avoid loosing enqueued commands.
Expecting users to implement these points themselves is
unrealistic and could result in code that performs poorly and
can't handle errors properly. To avoid all of that, Aedis
provides its own implementation. The general form of a program
that uses the high-level API looks like this
@code
int main()
{
net::io_context ioc;
client_type db(ioc.get_executor());
auto recv = std::make_shared<receiver>(db);
db.set_receiver(recv);
db.async_run("127.0.0.1", "6379", [](auto ec){ ... });
ioc.run();
}
@endcode
Users are concerned only with the implementation of the
receiver. For example
@code
// Callbacks.
struct receiver {
void on_resp3(command cmd, node<string_view> const& nd, error_code& ec) { ... }
void on_read(command cmd, std::size_t) { ... }
void on_push(std::size_t n) { }
void on_write(std::size_t n) { ... }
};
@endcode
The functions in the receiver above are callbacks that will be
called when events arrives
@li \c on_resp3: Called when a new chunk of resp3 data is parsed.
@li \c on_read: Called after the response to a command has been successfully read.
@li \c on_push: Called when a server push is received.
@li \c on_write: Called after a request has been successfully written to the stream.
The callbacks above are never called on errors, instead the \c
async_run function returns. Reconnection is also supported, for
example
@code
net::awaitable<void> run(std::shared_ptr<client_type> db)
{
auto ex = co_await net::this_coro::executor;
boost::asio::steady_timer timer{ex};
for (error_code ec;;) {
co_await db->async_run("127.0.0.1", "6379", redirect_error(use_awaitable, ec));
// Log the error.
std::clog << ec.message() << std::endl;
// Wait two seconds and try again.
timer.expires_after(std::chrono::seconds{2});
co_await timer.async_wait(redirect_error(use_awaitable, ec));
}
}
@endcode
when reconnecting the client will recover requests that haven't
been sent to Redis yet.
\subsection high-level-sending-cmds Sending commands
The db object from the example above can be passed around to other
objects so that commands can be sent from everywhere in the app.
Sending commands is also similar to what has been discussed before
@code
void foo(client<net::ip::tcp::socket>& db)
{
db.send(command::ping, "O rato roeu a roupa do rei de Roma");
db.send(command::incr, "counter");
db.send(command::set, "key", "Três pratos de trigo para três tigres");
db.send(command::get, "key");
...
}
@endcode
The \c send functions in this case will add commands to the output
queue and send them only if there is no pending response. This is
so because RESP3 is a request/response protocol, which means
clients must wait for responses before sending
the next request.
\section examples Examples
\b Basics: Focuses on small examples that show basic usage of
the library.
To better fix what has been said above, users should have a look at some simple examples.
- intro.cpp: A good starting point. Some commands are sent to the
Redis server and the responses are printed to screen.
\b Low \b level \b API (sync)
- transaction.cpp: Shows how to read the responses to a trasaction
efficiently. See also https://redis.io/topics/transactions.
@li intro_sync.cpp: Synchronous API usage example.
@li serialization_sync.cpp: Shows how serialize your own types.
- multipurpose_response.cpp: Shows how to read any responses to
Redis commands, including nested aggegagtes.
\b Low \b level \b API (async-coroutine)
- subscriber.cpp: Shows how channel subscription works at a low
level. See also https://redis.io/topics/pubsub.
@li subscriber.cpp: Shows how channel subscription works at the low level.
@li transaction.cpp: Shows how to read the response to transactions.
@li custom_adapter.cpp: Shows how to write a response adapter that prints to the screen, see \ref low-level-adapters.
- sync.cpp: Shows hot to use the Aedis synchronous api.
\b High \b level \b API (async only)
- key_expiration.cpp: Shows how to use \c std::optional to deal
with keys that may have expired or do not exist.
@li intro_high_level.cpp: High-level API usage example.
@li aggregates_high_level.cpp: Shows how receive RESP3 aggregate data types in a general way or in STL containers.
@li subscriber_high_level.cpp: Shows how channel [subscription](https://redis.io/topics/pubsub) works at a high-level.
\b STL \b Containers: Many of the Redis data structures can be
directly translated in to STL containers, below you will find some
example code. For a list of Redis data types see
https://redis.io/topics/data-types.
\b Asynchronous \b Servers (high-level API)
- hashes.cpp: Shows how to read Redis hashes in a \c std::map, \c
std::unordered_map and \c std::vector.
- lists.cpp: Shows how to read Redis lists in \c std::list,
\c std::deque, \c std::vector. It also illustrates basic serialization.
- sets.cpp: Shows how to read Redis sets in a \c std::set, \c
std::unordered_set and \c std::vector.
\b Customization \b points: Shows how de/serialize user types
avoiding copies. This is particularly useful for low latency
applications that want to avoid unneeded copies, for examples when
storing json strings in Redis keys.
- serialization.cpp: Shows how to de/serialize your own
non-aggregate data-structures.
- response_adapter.cpp: Customization point for users that want to
de/serialize their own data-structures like containers for example.
\b Asynchronous \b servers: Contains some non-trivial examples
servers that interact with users and Redis asynchronously over
long lasting connections using a higher level API.
- multipurpose_client.cpp: Shows how to use and experimental high
level redis client that keeps a long lasting connections to a
redis server. This is the starting point for the next examples.
- echo_server.cpp: Shows the basic principles behind asynchronous
communication with a database in an asynchronous server. In this
case, the server is a proxy between the user and Redis.
- chat_room.cpp: Shows how to build a scalable chat room that
scales to millions of users.
@li echo_server.cpp: Shows the basic principles behind asynchronous communication with a database in an asynchronous server.
@li chat_room.cpp: Shows how to build a scalable chat room.
\section using-aedis Using Aedis
@@ -99,43 +585,53 @@
- Boost 1.78 or greater.
- Unix Shell and Make.
- Compiler with C++20 coroutine support e.g. GCC 10 or greater.
- C++14. Some examples require C++20 with coroutine support.
- Redis server.
Some examples will also require interaction with
- redis-cli: used in one example.
- redis-cli: Used in one example.
- Redis Sentinel Server: used in some examples.
Aedis has been tested with the following compilers
- Tested with gcc: 7.5.0, 8.4.0, 9.3.0, 10.3.0.
- Tested with clang: 11.0.0, 10.0.0, 9.0.1, 8.0.1, 7.0.1.
\subsection Installation
Start by downloading and configuring the library
The first thing to do is to download and unpack Aedis
```
# Download the libray on github.
$ wget https://github.com/mzimbres/aedis/release-path # TODO
# Download the latest release on github
$ wget https://github.com/mzimbres/aedis/releases
# Uncompress the tarball and cd into the dir
$ tar -xzvf aedis-1.0.0.tar.gz && cd aedis-1.0.0
# Run configure with appropriate C++ flags and your boost
# installation, for example # You may also have to use
# -Wno-subobject-linkage on gcc.
$ CXXFLAGS="-std=c++20 -fcoroutines -g -Wall"\
./configure --prefix=/opt/aedis-1.0.0 --with-boost=/opt/boost_1_78_0 --with-boost-libdir=/opt/boost_1_78_0/lib
```
To install the library run
$ tar -xzvf aedis-version.tar.gz
```
If you can't use \c configure and \c make (e.g. Windows users)
you can already add the directory where you unpacked Aedis to the
include directories in your project, otherwise run
```
# See configure --help for all options.
$ ./configure --prefix=/opt/aedis-version --with-boost=/opt/boost_1_78_0
# Install Aedis in the path specified in --prefix
$ sudo make install
```
At this point you can start using Aedis. To build the examples and
test you can also run
and include the following header
```cpp
#include <aedis/src.hpp>
```
in exactly one source file in your applications. At this point you
can start using Aedis. To build the examples and run the tests run
```
# Build aedis examples.
@@ -144,18 +640,7 @@
# Test aedis in your machine.
$ make check
```
Finally you will have to include the following header
```cpp
#include <aedis/src.hpp>
```
in exactly one source file in your applications.
Windows users can use aedis by either adding the project root
directory to their include path or manually copying to another
location.
\subsection Developers
To generate the build system run
@@ -164,20 +649,199 @@
$ autoreconf -i
```
After that you will have a config in the project dir that you can
run as explained above, for example, to use a compiler other that
the system compiler use
After that you will have a configure script
that you can run as explained above, for example, to use a
compiler other that the system compiler run
```
CC=/opt/gcc-10.2.0/bin/gcc-10.2.0\
CXX=/opt/gcc-10.2.0/bin/g++-10.2.0\
CXXFLAGS="-std=c++20 -fcoroutines -g -Wall -Wno-subobject-linkage -Werror" ./configure ...
$ CC=/opt/gcc-10.2.0/bin/gcc-10.2.0 CXX=/opt/gcc-10.2.0/bin/g++-10.2.0 CXXFLAGS="-g -Wall -Werror" ./configure ...
$ make distcheck
```
\section Referece
\section why-aedis Why Aedis
At the time of this writing there are seventeen Redis clients
listed in the [official](https://redis.io/docs/clients/#cpp) list.
With so many clients available it is not unlikely that users are
asking themselves why yet another one. In this section I will try
to compare Aedis to the most popular clients and why we need
Aedis. Notice however that this is ongoing work as comparing
client objectively is difficult and time consuming.
The most popular client at the moment of this writing ranked by
github stars is
@li https://github.com/sewenew/redis-plus-plus
Before we start it is worth mentioning some of the things it does
not support
@li RESP3. Without RESP3 is impossible to support some important
Redis features like client side caching, among other things.
@li The Asio asynchronous model.
@li Serialization of user data types that avoids temporaries.
@li Error handling with error-code and exception overloads.
@li Healthy checks.
@li Fine control over memory allocation by means of allocators.
The remaining points will be addressed individually.
@subsection redis-plus-plus
Let us first have a look at what sending a command a pipeline and a
transaction look like
@code
auto redis = Redis("tcp://127.0.0.1:6379");
// Send commands
redis.set("key", "val");
auto val = redis.get("key"); // val is of type OptionalString.
if (val)
std::cout << *val << std::endl;
// Sending pipelines
auto pipe = redis.pipeline();
auto pipe_replies = pipe.set("key", "value")
.get("key")
.rename("key", "new-key")
.rpush("list", {"a", "b", "c"})
.lrange("list", 0, -1)
.exec();
// Parse reply with reply type and index.
auto set_cmd_result = pipe_replies.get<bool>(0);
// ...
// Sending a transaction
auto tx = redis.transaction();
auto tx_replies = tx.incr("num0")
.incr("num1")
.mget({"num0", "num1"})
.exec();
auto incr_result0 = tx_replies.get<long long>(0);
// ...
@endcode
Some of the problems with this API are
@li Heterogeneous treatment of commands, pipelines and transaction.
@li Having to manually finish the pipeline with \c .exec() is a major source of headache. This is not required by the protocol itself but results from the abstraction used.
@li Any Api that sends individual commands has a very restricted scope of usability and should be avoided in anything that needs minimum performance guarantees.
@li The API imposes exceptions on users, no error-code overload is provided.
@li No control over dynamic allocations.
@li No way to reuse the buffer for new calls to e.g. \c redis.get in order to avoid further dynamic memory allocations.
@li Error handling of resolve and connection no clear.
According to the documentation, pipelines in redis-plus-plus have
the following characteristics
> NOTE: By default, creating a Pipeline object is NOT cheap, since
> it creates a new connection.
This is clearly a downside of the API as pipelines should be the
default way of communicating and not an exception, paying such a
high price for each pipeline imposes a severe cost in performance.
Transactions also suffer from the very same problem
> NOTE: Creating a Transaction object is NOT cheap, since it
> creates a new connection.
In Aedis there is no difference between sending one command, a
pipeline or a transaction because creating the request is decoupled
from the IO objects, for example
@code
std::string request;
auto sr = make_serializer(request);
sr.push(command::hello, 3);
sr.push(command::multi);
sr.push(command::ping, "Some message.");
sr.push(command::set, "low-level-key", "some content", "EX", "2");
sr.push(command::exec);
sr.push(command::ping, "Another message.");
net::write(socket, net::buffer(request));
@endcode
The request created above will be sent to Redis in a single
pipeline and imposes no restriction on what it contains e.g. the
number of commands, transactions etc. The problems mentioned above
simply do not exist in Aedis. The way responses are read is
also more flexible
@code
std::string buffer;
auto dbuffer = net::dynamic_buffer(buffer);
std::tuple<std::string, boost::optional<std::string>> response;
resp3::read(socket, dbuffer); // hellp
resp3::read(socket, dbuffer); // multi
resp3::read(socket, dbuffer); // ping
resp3::read(socket, dbuffer); // set
resp3::read(socket, dbuffer, adapt(response));
resp3::read(socket, dbuffer); // quit
@endcode
@li The response objects are passed by the caller to the read
functions so that he has fine control over memory allocations and
object lifetime.
@li The user can either use error-code or exceptions.
@li Each response can be read individually in the response object
avoiding temporaries.
@li It is possible to ignore responses.
This was the blocking API, now let us compare the async interface
> redis-plus-plus also supports async interface, however, async
> support for Transaction and Subscriber is still on the way.
>
> The async interface depends on third-party event library, and so
> far, only libuv is supported.
Async code in redis-plus-plus looks like the following
@code
auto async_redis = AsyncRedis(opts, pool_opts);
Future<string> ping_res = async_redis.ping();
cout << ping_res.get() << endl;
@endcode
As the reader can see, the async interface is based on futures
which is also known to have a bad performance. The biggest
problem however with this async design is that it makes it
impossible to write asynchronous programs correctly since it
starts an async operation on every command sent instead of
enqueueing a message and triggering a write. It is also not clear
how are pipelines realised with the design (if at all).
In Aedis the send function looks like this
@code
template <class... Ts>
void client::send(Command cmd, Ts const&... args);
@endcode
and the response is delivered through a callback.
\section Acknowledgement
Some people that were helpful in the development of Aedis
@li Richard Hodges ([madmongo1](https://github.com/madmongo1)): For answering pretty much every question I had about Asio and the design of asynchronous programs.
@li Vinícius dos Santos Oliveira ([vinipsmaker](https://github.com/vinipsmaker)): For useful discussion about how Aedis consumes buffers in the read operation (among other things).
\section Reference
See \subpage any.
*/
/** \defgroup any Reference
*
* This page contains the documentation of all user facing code.
*/
#endif // AEDIS_HPP

View File

@@ -1,16 +0,0 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
// TODO: Remove this.
#include <boost/asio.hpp>
namespace aedis {
namespace net = boost::asio;
}

701
aedis/generic/client.hpp Normal file
View File

@@ -0,0 +1,701 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#ifndef AEDIS_GENERIC_CLIENT_HPP
#define AEDIS_GENERIC_CLIENT_HPP
#include <vector>
#include <limits>
#include <functional>
#include <iterator>
#include <algorithm>
#include <utility>
#include <chrono>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/steady_timer.hpp>
#include <aedis/resp3/type.hpp>
#include <aedis/resp3/node.hpp>
#include <aedis/redis/command.hpp>
#include <aedis/generic/detail/client_ops.hpp>
namespace aedis {
namespace generic {
/** \brief A high level Redis client.
* \ingroup any
*
* This class keeps a connection open to the Redis server where
* commands can be sent at any time. For more details, please see the
* documentation of each individual function.
*/
template <class AsyncReadWriteStream, class Command>
class client {
public:
/// Executor type.
using executor_type = typename AsyncReadWriteStream::executor_type;
/// Callback type of read operations.
using read_handler_type = std::function<void(Command cmd, std::size_t)>;
/// Callback type of write operations.
using write_handler_type = std::function<void(std::size_t)>;
/// Callback type of push operations.
using push_handler_type = std::function<void(std::size_t)>;
/// Callback type of resp3 operations.
using resp3_handler_type = std::function<void(Command, resp3::node<boost::string_view> const&, boost::system::error_code&)>;
using default_completion_token_type = boost::asio::default_completion_token_t<executor_type>;
/** @brief Configuration parameters.
*/
struct config {
/// Timeout of the \c async_resolve operation.
std::chrono::seconds resolve_timeout = std::chrono::seconds{5};
/// Timeout of the \c async_connect operation.
std::chrono::seconds connect_timeout = std::chrono::seconds{5};
/// Timeout of the \c async_read operation.
std::chrono::seconds read_timeout = std::chrono::seconds{5};
/// Timeout of the \c async_write operation.
std::chrono::seconds write_timeout = std::chrono::seconds{5};
/// Time after which a connection is considered idle if no data is received.
std::chrono::seconds idle_timeout = std::chrono::seconds{10};
/// The maximum size allwed in a read operation.
std::size_t max_read_size = (std::numeric_limits<std::size_t>::max)();
};
/** \brief Constructor.
*
* \param ex The executor.
* \param cfg Configuration parameters.
*/
client(boost::asio::any_io_executor ex, config cfg = config{})
: resv_{ex}
, read_timer_{ex}
, write_timer_{ex}
, wait_write_timer_{ex}
, check_idle_timer_{ex}
, cfg_{cfg}
, on_read_{[](Command, std::size_t){}}
, on_write_{[](std::size_t){}}
, on_push_{[](std::size_t){}}
, on_resp3_{[](Command, resp3::node<boost::string_view> const&, boost::system::error_code&) {}}
, sr_{requests_}
, last_data_{std::chrono::time_point<std::chrono::steady_clock>::min()}
, type_{resp3::type::invalid}
, cmd_info_{std::make_pair<Command>(Command::invalid, 0)}
{
if (cfg.idle_timeout < std::chrono::seconds{2})
cfg.idle_timeout = std::chrono::seconds{2};
}
/// Returns the executor.
auto get_executor() {return read_timer_.get_executor();}
/** @brief Adds a command to the output command queue.
*
* Adds a command to the end of the next request and signals the
* writer operation there is a new message awaiting to be sent.
* Otherwise the function is equivalent to serializer::push. @sa
* serializer.
*/
template <class... Ts>
void send(Command cmd, Ts const&... args)
{
auto const can_write = prepare_next();
auto const before = requests_.size();
sr_.push(cmd, args...);
auto const d = requests_.size() - before;
BOOST_ASSERT(d != 0);
info_.back().size += d;;
if (!has_push_response(cmd)) {
commands_.push_back(std::make_pair(cmd, d));
++info_.back().cmds;
}
if (can_write)
wait_write_timer_.cancel_one();
}
/** @brief Adds a command to the output command queue.
*
* Adds a command to the end of the next request and signals the
* writer operation there is a new message awaiting to be sent.
* Otherwise the function is equivalent to
* serializer::push_range2.
* @sa serializer.
*/
template <class Key, class ForwardIterator>
void send_range2(Command cmd, Key const& key, ForwardIterator begin, ForwardIterator end)
{
if (begin == end)
return;
auto const can_write = prepare_next();
auto const before = requests_.size();
sr_.push_range2(cmd, key, begin, end);
auto const d = requests_.size() - before;
BOOST_ASSERT(d != 0);
info_.back().size += d;
if (!has_push_response(cmd)) {
commands_.push_back(std::make_pair(cmd, d));
++info_.back().cmds;
}
if (can_write)
wait_write_timer_.cancel_one();
}
/** @brief Adds a command to the output command queue.
*
* Adds a command to the end of the next request and signals the
* writer operation there is a new message awaiting to be sent.
* Otherwise the function is equivalent to
* serializer::push_range2.
* @sa serializer.
*/
template <class ForwardIterator>
void send_range2(Command cmd, ForwardIterator begin, ForwardIterator end)
{
if (begin == end)
return;
auto const can_write = prepare_next();
auto const before = requests_.size();
sr_.push_range2(cmd, begin, end);
auto const d = requests_.size() - before;
BOOST_ASSERT(d != 0);
info_.back().size += d;
if (!has_push_response(cmd)) {
commands_.push_back(std::make_pair(cmd, d));
++info_.back().cmds;
}
if (can_write)
wait_write_timer_.cancel_one();
}
/** @brief Adds a command to the output command queue.
*
* Adds a command to the end of the next request and signals the
* writer operation there is a new message awaiting to be sent.
* Otherwise the function is equivalent to
* serializer::push_range.
* @sa serializer.
*/
template <class Key, class Range>
void send_range(Command cmd, Key const& key, Range const& range)
{
using std::begin;
using std::end;
send_range2(cmd, key, begin(range), end(range));
}
/** @brief Adds a command to the output command queue.
*
* Adds a command to the end of the next request and signals the
* writer operation there is a new message awaiting to be sent.
* Otherwise the function is equivalent to
* serializer::push_range.
* @sa serializer.
*/
template <class Range>
void send_range(Command cmd, Range const& range)
{
using std::begin;
using std::end;
send_range2(cmd, begin(range), end(range));
}
/** @brief Starts communication with the Redis server asynchronously.
*
* This function performs the following steps
*
* @li Resolves the Redis host as of \c async_resolve with the
* timeout passed in client::config::resolve_timeout.
*
* @li Connects to one of the endpoints returned by the resolve
* operation with the timeout passed in client::config::connect_timeout.
*
* @li Starts the \c async_read operation that keeps reading incoming
* responses. Each individual read uses the timeout passed on
* client::config::read_timeout. After each successful read it
* will call the read or push callback.
*
* @li Starts the \c async_write operation that waits for new commands
* to be sent to Redis. Each individual write uses the timeout
* passed on client::config::write_timeout. After a successful
* write it will call the write callback.
*
* @li Starts the check idle operation with the timeout specified
* in client::config::idle_timeout. If no data is received during
* that time interval \c async_run completes with
* generic::error::idle_timeout.
*
* @li Starts the healthy check operation that sends
* redis::command::ping to Redis with a frequency equal to
* client::config::idle_timeout / 2.
*
* In addition to the callbacks mentioned above, the read
* operations will call the resp3 callback as soon a new chunks of
* data become available to the user.
*
* It is safe to call \c async_run after it has returned. In this
* case, any outstanding commands will be sent after the
* connection is restablished. If a disconnect occurs while the
* response to a request has not been received, the client doesn't
* try to resend it to avoid resubmission.
*
* Example:
*
* @code
* awaitable<void> run_with_reconnect(std::shared_ptr<client_type> db)
* {
* auto ex = co_await this_coro::executor;
* asio::steady_timer timer{ex};
*
* for (error_code ec;;) {
* co_await db->async_run("127.0.0.1", "6379", redirect_error(use_awaitable, ec));
* timer.expires_after(std::chrono::seconds{2});
* co_await timer.async_wait(redirect_error(use_awaitable, ec));
* }
* }
* @endcode
*
* \param host Ip address or name of the Redis server.
* \param port Port where the Redis server is listening.
* \param token The completion token.
*
* The completion token must have the following signature
*
* @code
* void f(boost::system::error_code);
* @endcode
*
* \return This function returns only when there is an error.
*/
template <class CompletionToken = default_completion_token_type>
auto
async_run(
boost::string_view host = "127.0.0.1",
boost::string_view port = "6379",
CompletionToken token = CompletionToken{})
{
host_ = host;
port_ = port;
return boost::asio::async_compose
< CompletionToken
, void(boost::system::error_code)
>(detail::run_op<client>{this}, token, read_timer_, write_timer_, wait_write_timer_);
}
/// Set the read handler.
void set_read_handler(read_handler_type rh)
{ on_read_ = std::move(rh); }
/// Set the write handler.
void set_write_handler(write_handler_type wh)
{ on_write_ = std::move(wh); }
/// Set the push handler.
void set_push_handler(push_handler_type ph)
{ on_push_ = std::move(ph); }
/// Set the resp3 handler.
void set_resp3_handler(resp3_handler_type rh)
{ on_resp3_ = std::move(rh); }
/** @brief Convenience callback setter.
*
* Expects a class with the following member functions
*
* @code
* struct receiver {
* void on_resp3(Command cmd, resp3::node<boost::string_view> const& nd, boost::system::error_code& ec);
* void on_read(Command cmd, std::size_t);
* void on_write(std::size_t n);
* void on_push(std::size_t n);
* };
* @endcode
*/
template <class Receiver>
void set_receiver(std::shared_ptr<Receiver> recv)
{
on_resp3_ = [recv](Command cmd, resp3::node<boost::string_view> const& nd, boost::system::error_code& ec){recv->on_resp3(cmd, nd, ec);};
on_read_ = [recv](Command cmd, std::size_t n){recv->on_read(cmd, n);};
on_write_ = [recv](std::size_t n){recv->on_write(n);};
on_push_ = [recv](std::size_t n){recv->on_push(n);};
}
private:
using command_info_type = std::pair<Command, std::size_t>;
using time_point_type = std::chrono::time_point<std::chrono::steady_clock>;
template <class T, class V> friend struct detail::reader_op;
template <class T, class V> friend struct detail::ping_after_op;
template <class T> friend struct detail::read_op;
template <class T> friend struct detail::read_until_op;
template <class T> friend struct detail::writer_op;
template <class T> friend struct detail::write_op;
template <class T> friend struct detail::run_op;
template <class T> friend struct detail::connect_op;
template <class T> friend struct detail::resolve_op;
template <class T> friend struct detail::check_idle_op;
template <class T> friend struct detail::init_op;
template <class T> friend struct detail::read_write_check_op;
template <class T> friend struct detail::wait_for_data_op;
void on_resolve()
{
// If we are coming from a connection that was lost we have to
// reset the socket to a fresh state.
socket_ =
std::make_shared<AsyncReadWriteStream>(read_timer_.get_executor());
}
void on_connect()
{
// When we are reconnecting we can't simply call send(hello)
// as that will add the command to the end of the queue, we need
// it as the first element.
if (info_.empty()) {
// Either we are connecting for the first time or there are
// no commands that were left unresponded from the last
// connection. We can send hello as usual.
BOOST_ASSERT(requests_.empty());
BOOST_ASSERT(commands_.empty());
send(Command::hello, 3);
return;
}
if (info_.front().sent) {
// There is one request that was left unresponded when we
// e.g. lost the connection, since we erase requests right
// after writing them to the socket (to avoid resubmission) it
// is lost and we have to remove it.
// Noop if info_.front().size is already zero, which happens
// when the request was successfully writen to the socket.
// In the future we may want to avoid erasing but resend (at
// the risc of resubmission).
requests_.erase(0, info_.front().size);
// Erases the commands that were lost as well.
commands_.erase(
std::begin(commands_),
std::begin(commands_) + info_.front().cmds);
info_.front().cmds = 0;
// Do not erase the info_ front as we will use it below.
// info_.erase(std::begin(info_));
}
// Code below will add a hello to the front of the request and
// update info_ and commands_ accordingly.
auto const old_size = requests_.size();
sr_.push(Command::hello, 3);
auto const hello_size = requests_.size() - old_size;;
// Now we have to rotate the hello to the front of the request
// (Remember it must always be the first command).
std::rotate(
std::begin(requests_),
std::begin(requests_) + old_size,
std::end(requests_));
// Updates info_.
info_.front().size += hello_size;
info_.front().cmds += 1;
// Updates commands_
commands_.push_back(std::make_pair(Command::hello, hello_size));
std::rotate(
std::begin(commands_),
std::prev(std::end(commands_)),
std::end(commands_));
}
// Prepares the back of the queue to receive further commands. If
// true is returned the request in the front of the queue can be
// sent to the server.
bool prepare_next()
{
if (info_.empty()) {
info_.push_back({});
return true;
}
if (info_.front().sent) {
// There is a pending response, we can't modify the front of
// the vector.
BOOST_ASSERT(info_.front().cmds != 0);
if (info_.size() == 1)
info_.push_back({});
return false;
}
// When cmds = 0 there are only commands with push response on
// the request and we are not waiting for any response.
return info_.front().cmds == 0;
}
// Returns true when the next request can be written.
bool on_cmd(command_info_type)
{
BOOST_ASSERT(!info_.empty());
BOOST_ASSERT(!commands_.empty());
commands_.erase(std::begin(commands_));
if (--info_.front().cmds != 0)
return false;
info_.erase(std::begin(info_));
return !info_.empty();
}
// Resolves the address passed in async_run and store the results
// in endpoints_.
template <class CompletionToken = default_completion_token_type>
auto
async_resolve(CompletionToken&& token = default_completion_token_type{})
{
return boost::asio::async_compose
< CompletionToken
, void(boost::system::error_code)
>(detail::resolve_op<client>{this}, token, resv_.get_executor());
}
// Connects the socket to one of the endpoints in endpoints_ and
// stores the successful endpoint in endpoint_.
template <class CompletionToken = default_completion_token_type>
auto
async_connect(CompletionToken&& token = default_completion_token_type{})
{
return boost::asio::async_compose
< CompletionToken
, void(boost::system::error_code)
>(detail::connect_op<client>{this}, token, write_timer_.get_executor());
}
template <class CompletionToken = default_completion_token_type>
auto
async_read_until(CompletionToken&& token = default_completion_token_type{})
{
return boost::asio::async_compose
< CompletionToken
, void(boost::system::error_code)
>(detail::read_until_op<client>{this}, token, read_timer_.get_executor());
}
// Reads a complete resp3 response from the socket using the
// timeout config::read_timeout. On a successful read calls
// on_read_ or on_push_ depending on whether the response is a push
// or a response to a command.
template <class CompletionToken = default_completion_token_type>
auto
async_read(CompletionToken&& token = default_completion_token_type{})
{
return boost::asio::async_compose
< CompletionToken
, void(boost::system::error_code)
>(detail::read_op<client>{this}, token, read_timer_.get_executor());
}
// Loops on async_read described above.
template <class CompletionToken = default_completion_token_type>
auto
reader(CompletionToken&& token = default_completion_token_type{})
{
return boost::asio::async_compose
< CompletionToken
, void(boost::system::error_code)
>(detail::reader_op<client, Command>{this}, token, read_timer_.get_executor());
}
// Write with a timeout.
template <class CompletionToken = default_completion_token_type>
auto
async_write(
CompletionToken&& token = default_completion_token_type{})
{
return boost::asio::async_compose
< CompletionToken
, void(boost::system::error_code)
>(detail::write_op<client>{this}, token, write_timer_);
}
template <class CompletionToken = default_completion_token_type>
auto
writer(CompletionToken&& token = default_completion_token_type{})
{
return boost::asio::async_compose
< CompletionToken
, void(boost::system::error_code)
>(detail::writer_op<client>{this}, token, wait_write_timer_);
}
template <class CompletionToken = default_completion_token_type>
auto
async_init(CompletionToken&& token = default_completion_token_type{})
{
return boost::asio::async_compose
< CompletionToken
, void(boost::system::error_code)
>(detail::init_op<client>{this}, token, write_timer_, resv_);
}
template <class CompletionToken = default_completion_token_type>
auto
async_read_write_check(CompletionToken&& token = default_completion_token_type{})
{
return boost::asio::async_compose
< CompletionToken
, void(boost::system::error_code)
>(detail::read_write_check_op<client>{this}, token, read_timer_, write_timer_, wait_write_timer_, check_idle_timer_);
}
template <class CompletionToken = default_completion_token_type>
auto
async_ping_after(CompletionToken&& token = default_completion_token_type{})
{
return boost::asio::async_compose
< CompletionToken
, void(boost::system::error_code)
>(detail::ping_after_op<client, Command>{this}, token, read_timer_);
}
template <class CompletionToken = default_completion_token_type>
auto
async_wait_for_data(CompletionToken&& token = default_completion_token_type{})
{
return boost::asio::async_compose
< CompletionToken
, void(boost::system::error_code)
>(detail::wait_for_data_op<client>{this}, token, read_timer_);
}
template <class CompletionToken = default_completion_token_type>
auto
async_check_idle(CompletionToken&& token = default_completion_token_type{})
{
return boost::asio::async_compose
< CompletionToken
, void(boost::system::error_code)
>(detail::check_idle_op<client>{this}, token, check_idle_timer_);
}
void on_reader_exit()
{
socket_->close();
wait_write_timer_.expires_at(std::chrono::steady_clock::now());
}
// Stores information about a request.
struct info {
// Set to true before calling async_write.
bool sent = false;
// Request size in bytes. After a successful write it is set to
// zero.
std::size_t size = 0;
// The number of commands it contains. Commands with push
// responses are not counted.
std::size_t cmds = 0;
};
// Used to resolve the host on async_resolve.
boost::asio::ip::tcp::resolver resv_;
// The tcp socket.
std::shared_ptr<AsyncReadWriteStream> socket_;
// Timer used with async_read.
boost::asio::steady_timer read_timer_;
// Timer used with async_write.
boost::asio::steady_timer write_timer_;
// Timer that is canceled when a new message is added to the output
// queue.
boost::asio::steady_timer wait_write_timer_;
// Check idle timer.
boost::asio::steady_timer check_idle_timer_;
// Configuration parameters.
config cfg_;
// Called when a complete message is read.
read_handler_type on_read_;
// Called when a request has been written to the socket.
write_handler_type on_write_;
// Called when a complete push message is received.
push_handler_type on_push_;
// Called by the parser after each new chunk of resp3 data is
// processed.
resp3_handler_type on_resp3_;
// Buffer used by the read operations.
std::string read_buffer_;
// Requests payload and its serializer.
std::string requests_;
serializer<std::string> sr_;
// The commands contained in the requests.
std::vector<command_info_type> commands_;
// Info about the requests.
std::vector<info> info_;
// Last time we received data.
time_point_type last_data_;
// Used by the read_op.
resp3::type type_;
// Used by the read_op.
command_info_type cmd_info_;
// See async_connect.
boost::asio::ip::tcp::endpoint endpoint_;
// See async_resolve.
boost::asio::ip::tcp::resolver::results_type endpoints_;
// Host and port passed to async_run.
boost::string_view host_;
boost::string_view port_;
};
} // generic
} // aedis
#endif // AEDIS_GENERIC_CLIENT_HPP

View File

@@ -0,0 +1,554 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#ifndef AEDIS_GENERIC_CLIENT_OPS_HPP
#define AEDIS_GENERIC_CLIENT_OPS_HPP
#include <array>
#include <boost/system.hpp>
#include <boost/asio/write.hpp>
#include <boost/asio/connect.hpp>
#include <boost/core/ignore_unused.hpp>
#include <boost/assert.hpp>
#include <boost/asio/experimental/parallel_group.hpp>
#include <aedis/resp3/type.hpp>
#include <aedis/resp3/detail/parser.hpp>
#include <aedis/resp3/read.hpp>
#include <aedis/generic/error.hpp>
namespace aedis {
namespace generic {
namespace detail {
#include <boost/asio/yield.hpp>
template <class Client, class Command>
struct ping_after_op {
Client* cli;
boost::asio::coroutine coro;
template <class Self>
void
operator()(Self& self, boost::system::error_code ec = {})
{
reenter (coro)
{
BOOST_ASSERT((cli->cfg_.idle_timeout / 2) != std::chrono::seconds{0});
cli->read_timer_.expires_after(cli->cfg_.idle_timeout / 2);
yield cli->read_timer_.async_wait(std::move(self));
if (ec) {
self.complete(ec);
return;
}
// The timer fired, send the ping.
cli->send(Command::ping);
self.complete({});
}
}
};
template <class Client>
struct read_until_op {
Client* cli;
boost::asio::coroutine coro;
template <class Self>
void operator()( Self& self
, boost::system::error_code ec = {}
, std::size_t n = 0)
{
reenter (coro)
{
// Waits for incomming data.
yield
boost::asio::async_read_until(
*cli->socket_,
boost::asio::dynamic_buffer(cli->read_buffer_, cli->cfg_.max_read_size),
"\r\n",
std::move(self));
// Cancels the async_ping_after.
cli->read_timer_.cancel();
self.complete(ec);
}
}
};
template <class Client>
struct wait_for_data_op {
Client* cli;
boost::asio::coroutine coro;
template <class Self>
void operator()( Self& self
, std::array<std::size_t, 2> order = {}
, boost::system::error_code ec1 = {}
, boost::system::error_code ec2 = {})
{
reenter (coro)
{
yield
boost::asio::experimental::make_parallel_group(
[this](auto token) { return cli->async_read_until(token);},
[this](auto token) { return cli->async_ping_after(token);}
).async_wait(
boost::asio::experimental::wait_for_all(),
std::move(self));
// The order of completion is not important.
self.complete(ec1);
}
}
};
template <class Client>
struct check_idle_op {
Client* cli;
boost::asio::coroutine coro;
template <class Self>
void operator()(Self& self, boost::system::error_code ec = {})
{
reenter (coro) for(;;)
{
cli->check_idle_timer_.expires_after(cli->cfg_.idle_timeout);
yield cli->check_idle_timer_.async_wait(std::move(self));
if (ec) {
self.complete(ec);
return;
}
auto const now = std::chrono::steady_clock::now();
if (cli->last_data_ + cli->cfg_.idle_timeout < now) {
cli->on_reader_exit();
self.complete(error::idle_timeout);
return;
}
cli->last_data_ = now;
}
}
};
template <class Client>
struct resolve_op {
Client* cli;
boost::asio::coroutine coro;
template <class Self>
void
operator()( Self& self
, boost::system::error_code ec = {}
, boost::asio::ip::tcp::resolver::results_type res = {})
{
reenter (coro)
{
yield
cli->resv_.async_resolve(cli->host_.data(), cli->port_.data(), std::move(self));
if (ec) {
self.complete(ec);
return;
}
cli->endpoints_ = res;
self.complete({});
}
}
};
template <class Client>
struct connect_op {
Client* cli;
boost::asio::coroutine coro;
template <class Self>
void
operator()( Self& self
, boost::system::error_code ec = {}
, boost::asio::ip::tcp::endpoint const& ep = {})
{
reenter (coro)
{
yield
boost::asio::async_connect(
*cli->socket_,
cli->endpoints_,
std::move(self));
if (ec) {
self.complete(ec);
return;
}
cli->endpoint_ = ep;
self.complete({});
}
}
};
template <class Client>
struct init_op {
Client* cli;
boost::asio::coroutine coro;
template <class Self>
void operator()( Self& self
, std::array<std::size_t, 2> order = {}
, boost::system::error_code ec1 = {}
, boost::system::error_code ec2 = {})
{
reenter (coro)
{
// Tries to resolve with a timeout. We can use the writer
// timer here as there is no ongoing write operation.
cli->write_timer_.expires_after(cli->cfg_.resolve_timeout);
yield
boost::asio::experimental::make_parallel_group(
[this](auto token) { return cli->async_resolve(token);},
[this](auto token) { return cli->write_timer_.async_wait(token);}
).async_wait(
boost::asio::experimental::wait_for_one(),
std::move(self));
switch (order[0]) {
case 0:
{
if (ec1) {
self.complete(ec1);
return;
}
cli->on_resolve();
} break;
case 1:
{
if (!ec2) {
self.complete(generic::error::resolve_timeout);
return;
}
} break;
default: BOOST_ASSERT(false);
}
// Tries a connection with a timeout. We can use the writer
// timer here as there is no ongoing write operation.
cli->write_timer_.expires_after(cli->cfg_.connect_timeout);
yield
boost::asio::experimental::make_parallel_group(
[this](auto token) { return cli->async_connect(token);},
[this](auto token) { return cli->write_timer_.async_wait(token);}
).async_wait(
boost::asio::experimental::wait_for_one(),
std::move(self));
switch (order[0]) {
case 0:
{
if (ec1) {
self.complete(ec1);
return;
}
cli->on_connect();
} break;
case 1:
{
if (!ec2) {
self.complete(generic::error::connect_timeout);
return;
}
} break;
default: BOOST_ASSERT(false);
}
self.complete({});
}
}
};
template <class Client>
struct read_write_check_op {
Client* cli;
boost::asio::coroutine coro;
template <class Self>
void operator()( Self& self
, std::array<std::size_t, 3> order = {}
, boost::system::error_code ec1 = {}
, boost::system::error_code ec2 = {}
, boost::system::error_code ec3 = {})
{
reenter (coro)
{
// Starts the reader and writer ops.
cli->wait_write_timer_.expires_at(std::chrono::steady_clock::time_point::max());
yield
boost::asio::experimental::make_parallel_group(
[this](auto token) { return cli->writer(token);},
[this](auto token) { return cli->reader(token);},
[this](auto token) { return cli->async_check_idle(token);}
).async_wait(
boost::asio::experimental::wait_for_one_error(),
std::move(self));
switch (order[0]) {
case 0:
{
BOOST_ASSERT(ec1);
self.complete(ec1);
} break;
case 1:
{
BOOST_ASSERT(ec2);
self.complete(ec2);
} break;
case 2:
{
BOOST_ASSERT(ec3);
self.complete(ec3);
} break;
default: BOOST_ASSERT(false);
}
}
}
};
template <class Client>
struct run_op {
Client* cli;
boost::asio::coroutine coro;
template <class Self>
void operator()(Self& self, boost::system::error_code ec = {})
{
reenter (coro)
{
yield cli->async_init(std::move(self));
if (ec) {
self.complete(ec);
return;
}
yield cli->async_read_write_check(std::move(self));
if (ec) {
self.complete(ec);
return;
}
BOOST_ASSERT(false);
}
}
};
template <class Client>
struct write_op {
Client* cli;
boost::asio::coroutine coro;
template <class Self>
void operator()( Self& self
, std::array<std::size_t, 2> order = {}
, boost::system::error_code ec1 = {}
, std::size_t n = 0
, boost::system::error_code ec2 = {})
{
reenter (coro)
{
BOOST_ASSERT(!cli->info_.empty());
BOOST_ASSERT(cli->info_.front().size != 0);
BOOST_ASSERT(!cli->requests_.empty());
cli->write_timer_.expires_after(cli->cfg_.write_timeout);
cli->info_.front().sent = true;
yield
boost::asio::experimental::make_parallel_group(
[this](auto token) { return boost::asio::async_write(*cli->socket_, boost::asio::buffer(cli->requests_.data(), cli->info_.front().size), token);},
[this](auto token) { return cli->write_timer_.async_wait(token);}
).async_wait(
boost::asio::experimental::wait_for_one(),
std::move(self));
switch (order[0]) {
case 0:
{
if (ec1) {
self.complete(ec1);
return;
}
} break;
case 1:
{
if (!ec2) {
self.complete(generic::error::write_timeout);
return;
}
} break;
default: BOOST_ASSERT(false);
}
BOOST_ASSERT(!cli->info_.empty());
BOOST_ASSERT(cli->info_.front().size != 0);
BOOST_ASSERT(!cli->requests_.empty());
BOOST_ASSERT(n == cli->info_.front().size);
cli->requests_.erase(0, n);
cli->info_.front().size = 0;
if (cli->info_.front().cmds == 0)
cli->info_.erase(std::begin(cli->info_));
cli->on_write_(n);
self.complete({});
}
}
};
template <class Client>
struct writer_op {
Client* cli;
boost::asio::coroutine coro;
template <class Self>
void operator()(Self& self , boost::system::error_code ec = {})
{
reenter (coro) for (;;)
{
yield cli->async_write(std::move(self));
if (ec) {
cli->socket_->close();
self.complete(ec);
return;
}
yield cli->wait_write_timer_.async_wait(std::move(self));
if (!cli->socket_->is_open()) {
self.complete(error::write_stop_requested);
return;
}
}
}
};
template <class Client>
struct read_op {
Client* cli;
boost::asio::coroutine coro;
template <class Self>
void operator()( Self& self
, std::array<std::size_t, 2> order = {}
, boost::system::error_code ec1 = {}
, std::size_t n = 0
, boost::system::error_code ec2 = {})
{
reenter (coro)
{
cli->read_timer_.expires_after(cli->cfg_.read_timeout);
yield
boost::asio::experimental::make_parallel_group(
[this](auto token) { return resp3::async_read(*cli->socket_, boost::asio::dynamic_buffer(cli->read_buffer_, cli->cfg_.max_read_size), [cli_ = cli](resp3::node<boost::string_view> const& nd, boost::system::error_code& ec) mutable {cli_->on_resp3_(cli_->cmd_info_.first, nd, ec);}, token);},
[this](auto token) { return cli->read_timer_.async_wait(token);}
).async_wait(
boost::asio::experimental::wait_for_one(),
std::move(self));
switch (order[0]) {
case 0:
{
if (ec1) {
self.complete(ec1);
return;
}
} break;
case 1:
{
if (!ec2) {
self.complete(generic::error::read_timeout);
return;
}
} break;
default: BOOST_ASSERT(false);
}
if (cli->type_ == resp3::type::push) {
cli->on_push_(n);
} else {
if (cli->on_cmd(cli->cmd_info_))
cli->wait_write_timer_.cancel_one();
cli->on_read_(cli->cmd_info_.first, n);
}
self.complete({});
}
}
};
template <class Client, class Command>
struct reader_op {
Client* cli;
boost::asio::coroutine coro;
template <class Self>
void operator()( Self& self
, boost::system::error_code ec = {}
, std::size_t n = 0)
{
boost::ignore_unused(n);
reenter (coro) for (;;)
{
if (cli->read_buffer_.empty()) {
yield cli->async_wait_for_data(std::move(self));
if (ec) {
cli->on_reader_exit();
self.complete(ec);
return;
}
}
BOOST_ASSERT(!cli->read_buffer_.empty());
cli->type_ = resp3::to_type(cli->read_buffer_.front());
cli->cmd_info_ = std::make_pair(Command::invalid, 0);
if (cli->type_ != resp3::type::push) {
BOOST_ASSERT(!cli->commands_.empty());
cli->cmd_info_ = cli->commands_.front();
}
cli->last_data_ = std::chrono::steady_clock::now();
yield cli->async_read(std::move(self));
if (ec) {
cli->on_reader_exit();
self.complete(ec);
return;
}
}
}
};
#include <boost/asio/unyield.hpp>
} // detail
} // generic
} // aedis
#endif // AEDIS_GENERIC_CLIENT_OPS_HPP

54
aedis/generic/error.hpp Normal file
View File

@@ -0,0 +1,54 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#ifndef AEDIS_GENERIC_ERROR_HPP
#define AEDIS_GENERIC_ERROR_HPP
#include <boost/system/error_code.hpp>
namespace aedis {
namespace generic {
/** \brief Generic errors.
* \ingroup any
*/
enum class error
{
/// Represents the timeout of the resolve operation.
resolve_timeout = 1,
/// Represents the timeout of the connect operation.
connect_timeout,
/// Represents the timeout of the read operation.
read_timeout,
/// Represents the timeout of the write operation.
write_timeout,
/// Idle timeout.
idle_timeout,
/// Write stop requested.
write_stop_requested,
};
/** \brief Creates a error_code object from an error.
* \ingroup any
*/
boost::system::error_code make_error_code(error e);
} // generic
} // aedis
namespace std {
template<>
struct is_error_code_enum<::aedis::generic::error> : std::true_type {};
} // std
#endif // AEDIS_GENERIC_ERROR_HPP

View File

@@ -0,0 +1,48 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#include <aedis/generic/error.hpp>
namespace aedis {
namespace generic {
namespace detail {
struct error_category_impl : boost::system::error_category {
char const* name() const noexcept override
{
return "aedis.generic";
}
std::string message(int ev) const override
{
switch(static_cast<error>(ev)) {
case error::resolve_timeout: return "Resolve operation timeout.";
case error::connect_timeout: return "Connect operation timeout.";
case error::read_timeout: return "Read operation timeout.";
case error::write_timeout: return "Write operation timeout.";
case error::idle_timeout: return "Idle timeout.";
case error::write_stop_requested: return "Write stop requested.";
default: BOOST_ASSERT(false);
}
}
};
boost::system::error_category const& category()
{
static error_category_impl instance;
return instance;
}
} // detail
boost::system::error_code make_error_code(error e)
{
return boost::system::error_code{static_cast<int>(e), detail::category()};
}
} // generic
} // aedis

View File

@@ -0,0 +1,203 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#ifndef AEDIS_GENERIC_SERIALIZER_HPP
#define AEDIS_GENERIC_SERIALIZER_HPP
#include <boost/hana.hpp>
#include <aedis/resp3/compose.hpp>
// NOTE: Consider detecting tuples in the type in the parameter pack
// to calculate the header size correctly.
//
// NOTE: For some commands like hset it would be a good idea to assert
// the value type is a pair.
namespace aedis {
namespace generic {
/** @brief Creates Redis requests from user data.
* \ingroup any
*
* A request is composed of one or more redis commands and is
* referred to in the redis documentation as a pipeline, see
* https://redis.io/topics/pipelining.
*
* Example
*
* @code
* std::string request;
* auto sr = make_serializer(request);
* sr.push(command::hello, 3);
* sr.push(command::flushall);
* sr.push(command::ping);
* sr.push(command::incr, "key");
* sr.push(command::quit);
* co_await async_write(socket, buffer(request));
* @endcode
*
* \tparam Storage The storage type e.g \c std::string.
*
* \remarks Non-string types will be converted to string by using \c
* to_bulk, which must be made available over ADL.
*/
template <class Storage>
class serializer {
private:
Storage* request_;
public:
/** \brief Constructor
*
* \param storage The underlying storage object i.e. where the
* request is to be stored.
*/
serializer(Storage& storage) : request_(&storage) {}
/** @brief Appends a new command to the end of the request.
*
* For example
*
* \code
* std::string request;
* auto sr = make_serializer<command>(request);
* sr.push(command::set, "key", "some string", "EX", "2");
* \endcode
*
* will add the \c set command with value "some string" and an
* expiration of 2 seconds.
*
* \param cmd The command e.g redis or sentinel command.
* \param args Command arguments.
*/
template <class Command, class... Ts>
void push(Command cmd, Ts const&... args)
{
using boost::hana::for_each;
using boost::hana::make_tuple;
using resp3::type;
auto constexpr pack_size = sizeof...(Ts);
resp3::add_header(*request_, type::array, 1 + pack_size);
resp3::add_bulk(*request_, to_string(cmd));
resp3::add_bulk(*request_, make_tuple(args...));
}
/** @brief Appends a new command to the end of the request.
*
* This overload is useful for commands that have a key and have a
* dynamic range of arguments. For example
*
* @code
* std::map<std::string, std::string> map
* { {"key1", "value1"}
* , {"key2", "value2"}
* , {"key3", "value3"}
* };
*
* request req;
* req.push_range2(command::hset, "key", std::cbegin(map), std::cend(map));
* @endcode
*
* \param cmd The command e.g. Redis or Sentinel command.
* \param key The command key.
* \param begin Iterator to the begin of the range.
* \param end Iterator to the end of the range.
*/
template <class Command, class Key, class ForwardIterator>
void push_range2(Command cmd, Key const& key, ForwardIterator begin, ForwardIterator end)
{
using value_type = typename std::iterator_traits<ForwardIterator>::value_type;
using resp3::type;
if (begin == end)
return;
auto constexpr size = resp3::bulk_counter<value_type>::size;
auto const distance = std::distance(begin, end);
resp3::add_header(*request_, type::array, 2 + size * distance);
resp3::add_bulk(*request_, to_string(cmd));
resp3::add_bulk(*request_, key);
for (; begin != end; ++begin)
resp3::add_bulk(*request_, *begin);
}
/** @brief Appends a new command to the end of the request.
*
* This overload is useful for commands that have a dynamic number
* of arguments and don't have a key. For example
*
* \code
* std::set<std::string> channels
* { "channel1" , "channel2" , "channel3" }
*
* request req;
* req.push(command::subscribe, std::cbegin(channels), std::cedn(channels));
* \endcode
*
* \param cmd The Redis command
* \param begin Iterator to the begin of the range.
* \param end Iterator to the end of the range.
*/
template <class Command, class ForwardIterator>
void push_range2(Command cmd, ForwardIterator begin, ForwardIterator end)
{
using value_type = typename std::iterator_traits<ForwardIterator>::value_type;
using resp3::type;
if (begin == end)
return;
auto constexpr size = resp3::bulk_counter<value_type>::size;
auto const distance = std::distance(begin, end);
resp3::add_header(*request_, type::array, 1 + size * distance);
resp3::add_bulk(*request_, to_string(cmd));
for (; begin != end; ++begin)
resp3::add_bulk(*request_, *begin);
}
/** @brief Appends a new command to the end of the request.
*
* Equivalent to the overload taking a range (i.e. send_range2).
*/
template <class Command, class Key, class Range>
void push_range(Command cmd, Key const& key, Range const& range)
{
using std::begin;
using std::end;
push_range2(cmd, key, begin(range), end(range));
}
/** @brief Appends a new command to the end of the request.
*
* Equivalent to the overload taking a range (i.e. send_range2).
*/
template <class Command, class Range>
void push_range(Command cmd, Range const& range)
{
using std::begin;
using std::end;
push_range2(cmd, begin(range), end(range));
}
};
/** \brief Creates a serializer.
* \ingroup any
* \param storage The string.
*/
template <class CharT, class Traits, class Allocator>
auto make_serializer(std::basic_string<CharT, Traits, Allocator>& storage)
{
return serializer<std::basic_string<CharT, Traits, Allocator>>(storage);
}
} // generic
} // aedis
#endif // AEDIS_GENERIC_SERIALIZER_HPP

View File

@@ -1,27 +1,26 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#pragma once
#ifndef AEDIS_REDIS_COMMAND_HPP
#define AEDIS_REDIS_COMMAND_HPP
#include <ostream>
#include <string>
#include <aedis/resp3/serializer.hpp>
namespace aedis {
namespace redis {
/** \brief Redis commands.
* \ingroup any
*
* For a full list of commands see https://redis.io/commands.
* The full list of of commands can be found at
* https://redis.io/commands.
*
* \remark The list of commands below are read from Redis with the
* help of the command \c command.
* \remark This list was created with the help of the \c command
* command.
*/
enum class command {
/// https://redis.io/commands/acl
@@ -432,40 +431,27 @@ enum class command {
zscore,
/// https://redis.io/commands/zunionstore
zunionstore,
/// Unknown/invalid command.
unknown
/// Invalid command.
invalid
};
/** \brief Converts a command to a string
/** \brief Converts the command to a string.
* \ingroup any
*
* \param c The command to convert.
*/
char const* to_string(command c);
/** \brief Write the text for a command name to an output stream.
* \ingroup operators
*
/** \brief Writes the command string to the stream.
* \ingroup any
* \param os Output stream.
* \param c Redis command
*/
std::ostream& operator<<(std::ostream& os, command c);
/** \brief Returns true for commands with push response.
* \ingroup any
*/
// Checks whether a command has push response.
bool has_push_response(command cmd);
/** \brief Creates a serializer for a \c std::string.
* \ingroup any
* \param storage The string.
*/
template <class CharT, class Traits, class Allocator>
resp3::serializer<std::string, command>
make_serializer(std::basic_string<CharT, Traits, Allocator>& storage)
{
return resp3::serializer<std::basic_string<CharT, Traits, Allocator>, command>(storage);
}
} // redis
} // aedis
#endif // AEDIS_REDIS_COMMAND_HPP

View File

@@ -1,158 +0,0 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <queue>
#include <functional>
#include <aedis/aedis.hpp>
#include <aedis/redis/command.hpp>
namespace aedis {
namespace resp3 {
namespace experimental {
/** \brief A high level redis client.
* \ingroup any
*
* This Redis client keeps a connection to the database open and
* uses it for all communication with Redis. For examples on how to
* use see the examples chat_room.cpp, echo_server.cpp and redis_client.cpp.
*
* \remarks This class reuses its internal buffers for requests and
* for reading Redis responses. With time it will allocate less and
* less.
*/
class client : public std::enable_shared_from_this<client> {
public:
/** \brief The extended response adapter type.
*
* The difference between the adapter and extended_adapter
* concepts is that the extended get a command redis::parameter.
*/
using extented_adapter_type = std::function<void(redis::command, type, std::size_t, std::size_t, char const*, std::size_t, std::error_code&)>;
/// The type of the message callback.
using on_message_type = std::function<void(std::error_code ec, redis::command)>;
/// The type of the socket used by the client.
//using socket_type = net::use_awaitable_t<>::as_default_on_t<net::ip::tcp::socket>;
using socket_type = net::ip::tcp::socket;
private:
struct request_info {
// Request size in bytes.
std::size_t size = 0;
// The number of commands it contains excluding commands that
// have push types as responses, see has_push_response.
std::size_t cmds = 0;
};
// Requests payload.
std::string requests_;
// The commands contained in the requests.
std::queue<redis::command> commands_;
// Info about the requests.
std::queue<request_info> req_info_;
// The stream.
socket_type socket_;
// Timer used to inform the write coroutine that it can write the
// next message in the output queue.
net::steady_timer timer_;
// Response adapter.
extented_adapter_type extended_adapter_ = [](redis::command, type, std::size_t, std::size_t, char const*, std::size_t, std::error_code&) {};
// Message callback.
on_message_type on_msg_ = [](std::error_code ec, redis::command) {};
// Set when the writer coroutine should stop.
bool stop_writer_ = false;
// A coroutine that keeps reading the socket. When a message
// arrives it calls on_message.
net::awaitable<void> reader();
// Write coroutine. It is kept suspended until there are messages
// to be sent.
net::awaitable<void> writer();
/* Prepares the back of the queue to receive further commands.
*
* If true is returned the request in the front of the queue can be
* sent to the server. See async_write_some.
*/
bool prepare_next();
public:
/** \brief Client constructor.
*
* Constructos the client from an executor.
*
* \param ex The executor.
*/
client(net::any_io_executor ex);
/// Returns the executor used for I/O with Redis.
auto get_executor() {return socket_.get_executor();}
/** \brief Starts communication with Redis.
*
* This functions will send the hello command to Redis and spawn
* the read and write coroutines.
*
* \param socket A socket that is connected to redis.
*
* \returns This function returns an awaitable on which users should \c
* co_await. When the communication with Redis is lost the
* coroutine will finally co_return.
*/
net::awaitable<void> engage(socket_type socket);
/** \brief Adds a command to the command queue.
*
* \sa serializer.hpp
*/
template <class... Ts>
void send(redis::command cmd, Ts const&... args);
/// Sets an extended response adapter.
void set_extended_adapter(extented_adapter_type adapter);
/// Sets the message callback;
void set_msg_callback(on_message_type on_msg);
};
template <class... Ts>
void client::send(redis::command cmd, Ts const&... args)
{
auto const can_write = prepare_next();
auto sr = redis::make_serializer(requests_);
auto const before = std::size(requests_);
sr.push(cmd, args...);
auto const after = std::size(requests_);
req_info_.front().size += after - before;;
if (!has_push_response(cmd)) {
commands_.emplace(cmd);
++req_info_.front().cmds;
}
if (can_write)
timer_.cancel_one();
}
} // experimental
} // resp3
} // aedis

View File

@@ -1,200 +0,0 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <aedis/redis/experimental/client.hpp>
#include <boost/asio/experimental/awaitable_operators.hpp>
namespace aedis {
namespace resp3 {
namespace experimental {
client::client(net::any_io_executor ex)
: socket_{ex}
, timer_{ex}
{
timer_.expires_at(std::chrono::steady_clock::time_point::max());
}
net::awaitable<void> client::reader()
{
// Writes and reads continuosly from the socket.
for (std::string buffer;;) {
// Notice this coro can get scheduled while the write operation
// in the writer is ongoing. so we have to check.
while (!std::empty(req_info_) && req_info_.front().size != 0) {
assert(!std::empty(requests_));
boost::system::error_code ec;
co_await
net::async_write(
socket_,
net::buffer(requests_.data(), req_info_.front().size),
net::redirect_error(net::use_awaitable, ec));
requests_.erase(0, req_info_.front().size);
req_info_.front().size = 0;
if (req_info_.front().cmds != 0)
break; // We must await the responses.
req_info_.pop();
}
do { // Keeps reading while there are no messages queued waiting to be sent.
do { // Consumes the responses to all commands in the request.
boost::system::error_code ec;
auto const t =
co_await async_read_type(socket_, net::dynamic_buffer(buffer),
net::redirect_error(net::use_awaitable, ec));
if (ec) {
stop_writer_ = true;
timer_.cancel();
co_return;
}
if (t == type::push) {
auto adapter = [this](type t, std::size_t aggregate_size, std::size_t depth, char const* data, std::size_t size, std::error_code& ec)
{extended_adapter_(redis::command::unknown, t, aggregate_size, depth, data, size, ec);};
co_await resp3::async_read(socket_, net::dynamic_buffer(buffer), adapter, net::redirect_error(net::use_awaitable, ec));
on_msg_(ec, redis::command::unknown);
if (ec) { // TODO: Return only on non aedis errors.
stop_writer_ = true;
timer_.cancel();
co_return;
}
} else {
auto adapter = [this](type t, std::size_t aggregate_size, std::size_t depth, char const* data, std::size_t size, std::error_code& ec)
{extended_adapter_(commands_.front(), t, aggregate_size, depth, data, size, ec);};
boost::system::error_code ec;
co_await resp3::async_read(socket_, net::dynamic_buffer(buffer), adapter, net::redirect_error(net::use_awaitable, ec));
on_msg_(ec, commands_.front());
if (ec) { // TODO: Return only on non aedis errors.
stop_writer_ = true;
timer_.cancel();
co_return;
}
commands_.pop();
--req_info_.front().cmds;
}
} while (!std::empty(req_info_) && req_info_.front().cmds != 0);
// We may exit the loop above either because we are done
// with the response or because we received a server push
// while the queue was empty so we have to check before
// poping..
if (!std::empty(req_info_))
req_info_.pop();
} while (std::empty(req_info_));
}
}
net::awaitable<void> client::writer()
{
boost::system::error_code ec;
while (socket_.is_open()) {
ec = {};
co_await timer_.async_wait(net::redirect_error(net::use_awaitable, ec));
if (stop_writer_)
co_return;
// Notice this coro can get scheduled while the write operation
// in the reader is ongoing. so we have to check.
while (!std::empty(req_info_) && req_info_.front().size != 0) {
assert(!std::empty(requests_));
ec = {};
co_await net::async_write(
socket_, net::buffer(requests_.data(), req_info_.front().size),
net::redirect_error(net::use_awaitable, ec));
if (ec) {
// What should we do here exactly? Closing the socket will
// cause the reader coroutine to return so that the engage
// coroutine returns to the user.
socket_.close();
co_return;
}
requests_.erase(0, req_info_.front().size);
req_info_.front().size = 0;
if (req_info_.front().cmds != 0)
break;
req_info_.pop();
}
}
}
bool client::prepare_next()
{
if (std::empty(req_info_)) {
req_info_.push({});
return true;
}
if (req_info_.front().size == 0) {
// It has already been written and we are waiting for the
// responses.
req_info_.push({});
return false;
}
return false;
}
void client::set_extended_adapter(extented_adapter_type adapter)
{
extended_adapter_ = adapter;
}
void client::set_msg_callback(on_message_type on_msg)
{
on_msg_ = on_msg;
}
net::awaitable<void> client::engage(socket_type socket)
{
using namespace aedis::net::experimental::awaitable_operators;
socket_ = std::move(socket);
std::string request;
auto sr = redis::make_serializer(request);
sr.push(redis::command::hello, 3);
boost::system::error_code ec;
co_await net::async_write(socket_, net::buffer(request), net::redirect_error(net::use_awaitable, ec));
if (ec)
co_return;
std::string buffer;
auto adapter = [this](type t, std::size_t aggregate_size, std::size_t depth, char const* data, std::size_t size, std::error_code& ec)
{extended_adapter_(redis::command::hello, t, aggregate_size, depth, data, size, ec);};
co_await
resp3::async_read(
socket_, net::dynamic_buffer(buffer), adapter, net::redirect_error(net::use_awaitable, ec));
on_msg_(ec, redis::command::hello);
if (ec)
co_return;
co_await (reader() && writer());
}
} // experimental
} // resp3
} // aedis

View File

@@ -1,8 +1,7 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#include <aedis/redis/command.hpp>
@@ -217,6 +216,7 @@ char const* to_string(command c)
"ZSCAN",
"ZSCORE",
"ZUNIONSTORE",
"INVALID",
};
return table[static_cast<int>(c)];

View File

@@ -1,94 +0,0 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <aedis/resp3/response_traits.hpp>
namespace aedis {
namespace resp3 {
/** \brief Creates a void response adapter.
\ingroup any
The adapter returned by this function ignores responses and is
useful to avoid wasting time with responses which the user is
insterested in.
Example usage:
@code
co_await async_read(socket, buffer, adapt());
@endcode
*/
inline
auto adapt() noexcept
{ return response_traits<void>::adapt(); }
/** \brief Adapts user data to read operations.
* \ingroup any
*
* For example
* The following types are supported.
*
* - Integer data types e.g. `int`, `unsigned`, etc.
*
* - `std::string`
*
* We also support the following C++ containers
*
* - `std::vector<T>`. Can be used with any RESP3 aggregate type.
*
* - `std::deque<T>`. Can be used with any RESP3 aggregate type.
*
* - `std::list<T>`. Can be used with any RESP3 aggregate type.
*
* - `std::set<T>`. Can be used with RESP3 set type.
*
* - `std::unordered_set<T>`. Can be used with RESP3 set type.
*
* - `std::map<T>`. Can be used with RESP3 hash type.
*
* - `std::unordered_map<T>`. Can be used with RESP3 hash type.
*
* All these types can be wrapped in an `std::optional<T>`. This
* function also support \c std::tuple to read the response to
* tuples. At the moment this feature supports only transactions that
* contain simple types or aggregates that don't contain aggregates
* themselves (as in most cases).
*
* Example usage:
*
* @code
* std::unordered_map<std::string, std::string> cont;
* co_await async_read(socket, buffer, adapt(cont));
* @endcode
*
* For a transaction
*
* @code
sr.push(command::multi);
sr.push(command::ping, ...);
sr.push(command::incr, ...);
sr.push_range(command::rpush, ...);
sr.push(command::lrange, ...);
sr.push(command::incr, ...);
sr.push(command::exec);
co_await async_write(socket, buffer(request));
// Reads the response to a transaction
std::tuple<std::string, int, int, std::vector<std::string>, int> execs;
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(execs));
* @endcode
*/
template<class T>
auto adapt(T& t) noexcept
{ return response_traits<T>::adapt(t); }
} // resp3
} // aedis

View File

@@ -1,382 +0,0 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <set>
#include <optional>
#include <system_error>
#include <map>
#include <list>
#include <deque>
#include <vector>
#include <charconv>
#include <aedis/resp3/type.hpp>
#include <aedis/resp3/node.hpp>
#include <aedis/resp3/serializer.hpp>
#include <aedis/resp3/adapter/error.hpp>
namespace aedis {
namespace resp3 {
namespace adapter {
namespace detail {
template <class T>
typename std::enable_if<std::is_integral<T>::value, void>::type
from_string(
T& i,
char const* data,
std::size_t data_size,
std::error_code& ec)
{
auto const res = std::from_chars(data, data + data_size, i);
if (res.ec != std::errc())
ec = std::make_error_code(res.ec);
}
template <class CharT, class Traits, class Allocator>
void
from_string(
std::basic_string<CharT, Traits, Allocator>& s,
char const* data,
std::size_t data_size,
std::error_code&)
{
s.assign(data, data_size);
}
void set_on_resp3_error(type t, std::error_code& ec)
{
switch (t) {
case type::simple_error: ec = adapter::error::simple_error; return;
case type::blob_error: ec = adapter::error::blob_error; return;
case type::null: ec = adapter::error::null; return;
default: return;
}
}
// For optional responses.
void set_on_resp3_error2(type t, std::error_code& ec)
{
switch (t) {
case type::simple_error: ec = adapter::error::simple_error; return;
case type::blob_error: ec = adapter::error::blob_error; return;
default: return;
}
}
// Adapter that ignores responses.
struct ignore {
void
operator()(
type, std::size_t, std::size_t, char const*, std::size_t,
std::error_code&) { }
};
template <class Container>
class general {
private:
Container* result_;
public:
general(Container* c = nullptr): result_(c) {}
/** @brief Function called by the parser when new data has been processed.
*
* Users who what to customize their response types are required to derive
* from this class and override this function, see examples.
*
* \param t The RESP3 type of the data.
*
* \param n When t is an aggregate data type this will contain its size
* (see also element_multiplicity) for simple data types this is always 1.
*
* \param depth The element depth in the tree.
*
* \param data A pointer to the data.
*
* \param size The size of data.
*/
void
operator()(
type t,
std::size_t aggregate_size,
std::size_t depth,
char const* data,
std::size_t size,
std::error_code&)
{
result_->emplace_back(t, aggregate_size, depth, std::string{data, size});
}
};
template <class Node>
class adapter_node {
private:
Node* result_;
public:
adapter_node(Node* t = nullptr) : result_(t) {}
void
operator()(
type t,
std::size_t aggregate_size,
std::size_t depth,
char const* data,
std::size_t data_size,
std::error_code&)
{
result_->data_type = t;
result_->aggregate_size = aggregate_size;
result_->depth = depth;
result_->data.assign(data, data_size);
}
};
// Adapter for RESP3 simple data types.
template <class T>
class simple {
private:
T* result_;
public:
simple(T* t = nullptr) : result_(t) {}
void
operator()(
type t,
std::size_t aggregate_size,
std::size_t depth,
char const* data,
std::size_t data_size,
std::error_code& ec)
{
set_on_resp3_error(t, ec);
if (is_aggregate(t)) {
ec = adapter::error::expects_simple_type;
return;
}
assert(aggregate_size == 1);
from_string(*result_, data, data_size, ec);
}
};
template <class T>
class simple_optional {
private:
std::optional<T>* result_;
public:
simple_optional(std::optional<T>* o = nullptr) : result_(o) {}
void
operator()(
type t,
std::size_t aggregate_size,
std::size_t depth,
char const* data,
std::size_t data_size,
std::error_code& ec)
{
set_on_resp3_error2(t, ec);
if (is_aggregate(t)) {
ec = adapter::error::expects_simple_type;
return;
}
assert(aggregate_size == 1);
if (depth != 0) {
ec = adapter::error::nested_unsupported;
return;
}
if (t == type::null)
return;
if (!result_->has_value())
*result_ = T{};
from_string(result_->value(), data, data_size, ec);
}
};
/* A std::vector adapter.
*/
template <class Container>
class vector {
private:
int i_ = -1;
Container* result_;
public:
vector(Container* v = nullptr) : result_{v} {}
void
operator()(type t,
std::size_t aggregate_size,
std::size_t depth,
char const* data,
std::size_t data_size,
std::error_code& ec)
{
set_on_resp3_error(t, ec);
if (is_aggregate(t)) {
if (i_ != -1) {
ec = adapter::error::nested_unsupported;
return;
}
auto const m = element_multiplicity(t);
result_->resize(m * aggregate_size);
++i_;
} else {
assert(aggregate_size == 1);
from_string(result_->at(i_), data, data_size, ec);
++i_;
}
}
};
template <class Container>
class list {
private:
Container* result_;
public:
list(Container* ref = nullptr): result_(ref) {}
void
operator()(type t,
std::size_t aggregate_size,
std::size_t depth,
char const* data,
std::size_t data_size,
std::error_code& ec)
{
set_on_resp3_error(t, ec);
if (is_aggregate(t)) {
if (depth != 0) {
ec = adapter::error::nested_unsupported;
return;
}
return;
}
assert(aggregate_size == 1);
if (depth != 1) {
ec = adapter::error::nested_unsupported;
return;
}
result_->push_back({});
from_string(result_->back(), data, data_size, ec);
}
};
template <class Container>
class set {
private:
Container* result_;
typename Container::iterator hint_;
public:
set(Container* c = nullptr)
: result_(c)
, hint_(std::end(*c))
{}
void
operator()(type t,
std::size_t aggregate_size,
std::size_t depth,
char const* data,
std::size_t data_size,
std::error_code& ec)
{
set_on_resp3_error(t, ec);
if (t == type::set) {
assert(depth == 0);
return;
}
assert(!is_aggregate(t));
assert(depth == 1);
assert(aggregate_size == 1);
typename Container::key_type obj;
from_string(obj, data, data_size, ec);
if (hint_ == std::end(*result_)) {
auto const ret = result_->insert(std::move(obj));
hint_ = ret.first;
} else {
hint_ = result_->insert(hint_, std::move(obj));
}
}
};
template <class Container>
class map {
private:
Container* result_;
typename Container::iterator current_;
bool on_key_ = true;
public:
map(Container* c = nullptr)
: result_(c)
, current_(std::end(*c))
{}
void
operator()(type t,
std::size_t aggregate_size,
std::size_t depth,
char const* data,
std::size_t data_size,
std::error_code& ec)
{
set_on_resp3_error(t, ec);
if (t == type::map) {
assert(depth == 0);
return;
}
assert(!is_aggregate(t));
assert(depth == 1);
assert(aggregate_size == 1);
if (on_key_) {
typename Container::key_type obj;
from_string(obj, data, data_size, ec);
current_ = result_->insert(current_, {std::move(obj), {}});
} else {
typename Container::mapped_type obj;
from_string(obj, data, data_size, ec);
current_->second = std::move(obj);
}
on_key_ = !on_key_;
}
};
} // detail
} // adapter
} // resp3
} // aedis

View File

@@ -1,87 +0,0 @@
#pragma once
# include <system_error>
namespace aedis {
namespace resp3 {
namespace adapter {
/** \brief Errors that may occurr when reading a response.
* \ingroup any
*/
enum class error
{
/// Expects a simple RESP3 type but got an aggregate.
expects_simple_type = 1,
/// Nested response not supported.
nested_unsupported,
/// Got RESP3 simple error.
simple_error,
/// Got RESP3 blob_error.
blob_error,
/// The tuple used as response has incompatible size.
incompatible_tuple_size,
/// Got RESP3 null type.
null
};
namespace detail {
struct error_category_impl : std::error_category {
/// \todo Fix string lifetime.
char const* name() const noexcept override
{ return "aedis.response_adapter"; }
// TODO: Move to .ipp
std::string message(int ev) const override
{
switch(static_cast<error>(ev)) {
case error::expects_simple_type: return "Expects a simple RESP3 type";
case error::nested_unsupported: return "Nested responses unsupported.";
case error::simple_error: return "Got RESP3 simple-error.";
case error::blob_error: return "Got RESP3 blob-error.";
case error::incompatible_tuple_size: return "The tuple used as response has incompatible size.";
case error::null: return "Got RESP3 null.";
default: assert(false);
}
}
};
inline
std::error_category const& adapter_category()
{
static error_category_impl instance;
return instance;
}
} // detail
inline
std::error_code make_error_code(error e)
{
static detail::error_category_impl const eci{};
return std::error_code{static_cast<int>(e), detail::adapter_category()};
}
inline
std::error_condition make_error_condition(error e)
{
return std::error_condition(static_cast<int>(e), detail::adapter_category());
}
} // adapter
} // resp3
} // aedis
namespace std {
template<>
struct is_error_code_enum<::aedis::resp3::adapter::error> : std::true_type {};
} // std

159
aedis/resp3/compose.hpp Normal file
View File

@@ -0,0 +1,159 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#ifndef AEDIS_RESP3_COMPOSE_HPP
#define AEDIS_RESP3_COMPOSE_HPP
#include <string>
#include <tuple>
#include <boost/hana.hpp>
#include <boost/utility/string_view.hpp>
#include <aedis/resp3/type.hpp>
namespace aedis {
namespace resp3 {
constexpr char separator[] = "\r\n";
/** @brief Adds a bulk to the request.
* @ingroup any
*
* This function is useful in serialization of your own data
* structures in a request. For example
*
* @code
* void to_bulk(std::string& to, mystruct const& obj)
* {
* auto const str = // Convert obj to a string.
* resp3::to_bulk(to, str);
* }
* @endcode
*
* See more in \ref requests-serialization.
*/
template <class Request>
void to_bulk(Request& to, boost::string_view data)
{
auto const str = std::to_string(data.size());
to += to_code(type::blob_string);
to.append(std::cbegin(str), std::cend(str));
to += separator;
to.append(std::cbegin(data), std::cend(data));
to += separator;
}
template <class Request, class T, typename = typename std::enable_if<std::is_integral<T>::value>::type>
void to_bulk(Request& to, T n)
{
auto const s = std::to_string(n);
to_bulk(to, boost::string_view{s});
}
namespace detail {
template <class T>
struct add_bulk_impl {
template <class Request>
static void add(Request& to, T const& from)
{
using namespace aedis::resp3;
to_bulk(to, from);
}
};
template <class U, class V>
struct add_bulk_impl<std::pair<U, V>> {
template <class Request>
static void add(Request& to, std::pair<U, V> const& from)
{
using namespace aedis::resp3;
to_bulk(to, from.first);
to_bulk(to, from.second);
}
};
template <class ...Ts>
struct add_bulk_impl<boost::hana::tuple<Ts...>> {
template <class Request>
static void add(Request& to, boost::hana::tuple<Ts...> const& from)
{
using boost::hana::for_each;
// Fold expressions is C++17 so we use hana.
//(resp3::add_bulk(*request_, args), ...);
for_each(from, [&](auto const& e) {
using namespace aedis::resp3;
to_bulk(to, e);
});
}
};
} // detail
/** @brief Adds a resp3 header to the request.
* @ingroup any
*
* See mystruct.hpp for an example.
*/
template <class Request>
void add_header(Request& to, type t, std::size_t size)
{
auto const str = std::to_string(size);
to += to_code(t);
to.append(std::cbegin(str), std::cend(str));
to += separator;
}
/* Adds a rep3 bulk to the request.
*
* This function adds \c data as a bulk string to the request \c to.
*/
template <class Request, class T>
void add_bulk(Request& to, T const& data)
{
detail::add_bulk_impl<T>::add(to, data);
}
template <class>
struct bulk_counter;
template <class>
struct bulk_counter {
static constexpr auto size = 1U;
};
template <class T, class U>
struct bulk_counter<std::pair<T, U>> {
static constexpr auto size = 2U;
};
template <class Request>
void add_blob(Request& to, boost::string_view blob)
{
to.append(std::cbegin(blob), std::cend(blob));
to += separator;
}
/** @brief Adds a separator to the request.
* @ingroup any
*
* See mystruct.hpp for an example.
*/
template <class Request>
void add_separator(Request& to)
{
to += separator;
}
} // resp3
} // aedis
#endif // AEDIS_RESP3_COMPOSE_HPP

View File

@@ -1,87 +0,0 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <string>
#include <string_view>
#include <utility>
namespace aedis {
namespace resp3 {
namespace detail {
template <class>
struct needs_to_string : std::true_type {};
template <> struct needs_to_string<std::string> : std::false_type {};
template <> struct needs_to_string<std::string_view> : std::false_type {};
template <> struct needs_to_string<char const*> : std::false_type {};
template <> struct needs_to_string<char*> : std::false_type {};
template <std::size_t N>
struct needs_to_string<char[N]> : std::false_type {};
template <std::size_t N>
struct needs_to_string<char const[N]> : std::false_type {};
template <class Storage>
void add_header(Storage& to, int size)
{
// std::string does not support allocators.
using std::to_string;
auto const str = to_string(size);
to += "*";
to.append(std::cbegin(str), std::cend(str));
to += "\r\n";
}
template <class Storage>
void add_bulk(Storage& to, std::string_view data)
{
// std::string does not support allocators.
using std::to_string;
auto const str = to_string(std::size(data));
to += "$";
to.append(std::cbegin(str), std::cend(str));
to += "\r\n";
to += data;
to += "\r\n";
}
template <class Storage, class T>
void add_bulk(Storage& to, T const& data, typename std::enable_if<needs_to_string<T>::value, bool>::type = false)
{
using std::to_string;
auto const s = to_string(data);
add_bulk(to, s);
}
// Overload for pairs.
// TODO: Overload for tuples.
template <class Storage, class T1, class T2>
void add_bulk(Storage& to, std::pair<T1, T2> const& pair)
{
add_bulk(to, pair.first);
add_bulk(to, pair.second);
}
template <class>
struct value_type_size {
static constexpr auto size = 1U;
};
template <class T, class U>
struct value_type_size<std::pair<T, U>> {
static constexpr auto size = 2U;
};
} // detail
} // resp3
} // aedis

View File

@@ -1,10 +1,12 @@
/* Copyright (c) 2019 - 2021 Marcelo Zimbres Silva (mzimbres at gmail dot com)
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/home/x3.hpp>
#include <aedis/resp3/detail/parser.hpp>
#include <aedis/resp3/type.hpp>
@@ -12,27 +14,15 @@ namespace aedis {
namespace resp3 {
namespace detail {
type to_type(char c)
std::size_t
parse_uint(char const* data, std::size_t size, boost::system::error_code& ec)
{
switch (c) {
case '!': return type::blob_error;
case '=': return type::verbatim_string;
case '$': return type::blob_string;
case ';': return type::streamed_string_part;
case '-': return type::simple_error;
case ':': return type::number;
case ',': return type::doublean;
case '#': return type::boolean;
case '(': return type::big_number;
case '+': return type::simple_string;
case '_': return type::null;
case '>': return type::push;
case '~': return type::set;
case '*': return type::array;
case '|': return type::attribute;
case '%': return type::map;
default: return type::invalid;
}
static constexpr boost::spirit::x3::uint_parser<std::size_t, 10> p{};
std::size_t ret;
if (!parse(data, data + size, p, ret))
ec = error::not_a_number;
return ret;
}
} // detail

View File

@@ -1,29 +1,31 @@
/* Copyright (c) 2019 - 2021 Marcelo Zimbres Silva (mzimbres at gmail dot com)
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#pragma once
#ifndef AEDIS_RESP3_PARSER_HPP
#define AEDIS_RESP3_PARSER_HPP
#include <string_view>
#include <charconv>
#include <system_error>
#include <limits>
#include <system_error>
#include <boost/assert.hpp>
#include <boost/utility/string_view.hpp>
#include <aedis/resp3/error.hpp>
#include <aedis/resp3/node.hpp>
namespace aedis {
namespace resp3 {
namespace detail {
// Converts a wire-format RESP3 type (char) to a resp3 type.
type to_type(char c);
std::size_t parse_uint(char const* data, std::size_t size, boost::system::error_code& ec);
template <class ResponseAdapter>
class parser {
private:
using node_type = node<boost::string_view>;
static constexpr std::size_t max_embedded_depth = 5;
ResponseAdapter adapter_;
@@ -54,24 +56,21 @@ public:
// Returns the number of bytes that have been consumed.
std::size_t
advance(char const* data, std::size_t n, std::error_code& ec)
consume(char const* data, std::size_t n, boost::system::error_code& ec)
{
if (bulk_ != type::invalid) {
n = bulk_length_ + 2;
switch (bulk_) {
case type::streamed_string_part:
{
if (bulk_length_ == 0) {
sizes_[depth_] = 1;
} else {
adapter_(bulk_, 1, depth_, data, bulk_length_, ec);
if (ec)
return 0;
}
BOOST_ASSERT(bulk_length_ != 0);
adapter_({bulk_, 1, depth_, {data, bulk_length_}}, ec);
if (ec)
return 0;
} break;
default:
{
adapter_(bulk_, 1, depth_, data, bulk_length_, ec);
adapter_({bulk_, 1, depth_, {data, bulk_length_}}, ec);
if (ec)
return 0;
}
@@ -83,46 +82,75 @@ public:
} else if (sizes_[depth_] != 0) {
auto const t = to_type(*data);
switch (t) {
case type::blob_error:
case type::verbatim_string:
case type::streamed_string_part:
{
auto const r =
std::from_chars(data + 1, data + n - 2, bulk_length_);
if (r.ec != std::errc()) {
ec = error::not_a_number;
bulk_length_ = parse_uint(data + 1, n - 2, ec);
if (ec)
return 0;
}
bulk_ = t;
if (bulk_length_ == 0) {
adapter_({type::streamed_string_part, 1, depth_, {}}, ec);
sizes_[depth_] = 0; // We are done.
} else {
bulk_ = type::streamed_string_part;
}
} break;
case type::blob_error:
case type::verbatim_string:
case type::blob_string:
{
if (*(data + 1) == '?') {
// NOTE: This can only be triggered with blob_string.
// Trick: A streamed string is read as an aggregate
// of infinite lenght. When the streaming is done
// the server is supposed to send a part with lenght
// the server is supposed to send a part with length
// 0.
sizes_[++depth_] = (std::numeric_limits<std::size_t>::max)();
} else {
auto const r =
std::from_chars(data + 1, data + n - 2, bulk_length_);
if (r.ec != std::errc()) {
ec = error::not_a_number;
return 0;
}
bulk_length_ = parse_uint(data + 1, n - 2, ec);
if (ec)
return 0;
bulk_ = type::blob_string;
bulk_ = t;
}
} break;
case type::simple_error:
case type::number:
case type::doublean:
case type::boolean:
{
if (n == 3) {
ec = error::empty_field;
return 0;
}
if (*(data + 1) != 'f' && *(data + 1) != 't') {
ec = error::unexpected_bool_value;
return 0;
}
adapter_({t, 1, depth_, {data + 1, n - 3}}, ec);
if (ec)
return 0;
--sizes_[depth_];
} break;
case type::doublean:
case type::big_number:
case type::number:
{
if (n == 3) {
ec = error::empty_field;
return 0;
}
adapter_({t, 1, depth_, {data + 1, n - 3}}, ec);
if (ec)
return 0;
--sizes_[depth_];
} break;
case type::simple_error:
case type::simple_string:
{
adapter_(t, 1, depth_, data + 1, n - 3, ec);
adapter_({t, 1, depth_, {data + 1, n - 3}}, ec);
if (ec)
return 0;
@@ -130,7 +158,7 @@ public:
} break;
case type::null:
{
adapter_(type::null, 1, depth_, nullptr, 0, ec);
adapter_({type::null, 1, depth_, {}}, ec);
if (ec)
return 0;
@@ -142,14 +170,11 @@ public:
case type::attribute:
case type::map:
{
std::size_t l;
auto const r = std::from_chars(data + 1, data + n - 2, l);
if (r.ec != std::errc()) {
ec = error::not_a_number;
return 0;
}
auto const l = parse_uint(data + 1, n - 2, ec);
if (ec)
return 0;
adapter_(t, l, depth_, nullptr, 0, ec);
adapter_({t, l, depth_, {}}, ec);
if (ec)
return 0;
@@ -197,3 +222,5 @@ public:
} // detail
} // resp3
} // aedis
#endif // AEDIS_RESP3_PARSER_HPP

View File

@@ -1,23 +1,31 @@
/* Copyright (c) 2019 - 2021 Marcelo Zimbres Silva (mzimbres at gmail dot com)
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#pragma once
#ifndef AEDIS_RESP3_READ_OPS_HPP
#define AEDIS_RESP3_READ_OPS_HPP
#include <string_view>
#include <aedis/config.hpp>
#include <aedis/resp3/detail/parser.hpp>
#include <boost/assert.hpp>
#include <boost/asio/read.hpp>
#include <boost/asio/read_until.hpp>
#include <boost/asio/coroutine.hpp>
#include <boost/core/ignore_unused.hpp>
#include <boost/utility/string_view.hpp>
#include <aedis/resp3/detail/parser.hpp>
namespace aedis {
namespace resp3 {
namespace detail {
// TODO: Use asio::coroutine.
#include <boost/asio/yield.hpp>
struct ignore_response {
void operator()(node<boost::string_view>, boost::system::error_code&) { }
};
template <
class AsyncReadStream,
class DynamicBuffer,
@@ -28,7 +36,8 @@ private:
DynamicBuffer buf_;
parser<ResponseAdapter> parser_;
std::size_t consumed_;
int start_;
std::size_t buffer_size_;
boost::asio::coroutine coro_;
public:
parse_op(AsyncReadStream& stream, DynamicBuffer buf, ResponseAdapter adapter)
@@ -36,7 +45,6 @@ public:
, buf_ {buf}
, parser_ {adapter}
, consumed_{0}
, start_{1}
{ }
template <class Self>
@@ -44,15 +52,16 @@ public:
, boost::system::error_code ec = {}
, std::size_t n = 0)
{
switch (start_) {
for (;;) {
if (parser_.bulk() == type::invalid) {
case 1:
start_ = 0;
net::async_read_until(stream_, buf_, "\r\n", std::move(self));
reenter (coro_) for (;;) {
if (parser_.bulk() == type::invalid) {
yield
boost::asio::async_read_until(stream_, buf_, "\r\n", std::move(self));
if (ec) {
self.complete(ec, 0);
return;
}
} else {
// On a bulk read we can't read until delimiter since the
// payload may contain the delimiter itself so we have to
// read the whole chunk. However if the bulk blob is small
@@ -60,78 +69,47 @@ public:
// read), in which case there is no need of initiating
// another async op, otherwise we have to read the missing
// bytes.
if (std::size(buf_) < (parser_.bulk_length() + 2)) {
start_ = 0;
auto const s = std::size(buf_);
auto const l = parser_.bulk_length();
auto const to_read = l + 2 - s;
buf_.grow(to_read);
net::async_read(stream_, buf_.data(s, to_read), net::transfer_all(), std::move(self));
return;
if (buf_.size() < (parser_.bulk_length() + 2)) {
buffer_size_ = buf_.size();
buf_.grow(parser_.bulk_length() + 2 - buffer_size_);
yield
boost::asio::async_read(
stream_,
buf_.data(buffer_size_, parser_.bulk_length() + 2 - buffer_size_),
boost::asio::transfer_all(),
std::move(self));
if (ec) {
self.complete(ec, 0);
return;
}
}
default:
{
if (ec) {
self.complete(ec, 0);
return;
}
n = parser_.bulk_length() + 2;
BOOST_ASSERT(buf_.size() >= n);
}
n = parser_.advance((char const*)buf_.data(0, n).data(), n, ec);
if (ec) {
self.complete(ec, 0);
return;
}
n = parser_.consume((char const*)buf_.data(0, n).data(), n, ec);
if (ec) {
self.complete(ec, 0);
return;
}
buf_.consume(n);
consumed_ += n;
if (parser_.done()) {
self.complete({}, consumed_);
return;
}
}
buf_.consume(n);
consumed_ += n;
if (parser_.done()) {
self.complete({}, consumed_);
return;
}
}
}
};
// TODO: Use asio::coroutine.
template <class AsyncReadStream, class DynamicBuffer>
class type_op {
private:
AsyncReadStream& stream_;
DynamicBuffer buf_;
public:
type_op(AsyncReadStream& stream, DynamicBuffer buf)
: stream_ {stream}
, buf_ {buf}
{ }
template <class Self>
void operator()( Self& self
, boost::system::error_code ec = {}
, std::size_t n = 0)
{
boost::ignore_unused(n);
if (ec) {
self.complete(ec, type::invalid);
return;
}
if (std::size(buf_) == 0) {
net::async_read_until(stream_, buf_, "\r\n", std::move(self));
return;
}
auto const* data = (char const*)buf_.data(0, n).data();
auto const type = to_type(*data);
self.complete(ec, type);
return;
}
};
#include <boost/asio/unyield.hpp>
} // detail
} // resp3
} // aedis
#endif // AEDIS_RESP3_READ_OPS_HPP

View File

@@ -1,13 +1,18 @@
#pragma once
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
# include <system_error>
#ifndef AEDIS_RESP3_ERROR_HPP
#define AEDIS_RESP3_ERROR_HPP
/// \file error.hpp
#include <boost/system/error_code.hpp>
namespace aedis {
namespace resp3 {
/** \brief RESP3 parsing errors.
/** \brief RESP3 errors.
* \ingroup any
*/
enum class error
@@ -15,59 +20,26 @@ enum class error
/// Invalid RESP3 type.
invalid_type = 1,
/// Can't parse the string as an integer.
/// Can't parse the string as a number.
not_a_number,
/// Received less bytes than expected.
unexpected_read_size,
/// The maximum depth of a nested response was exceeded.
exceeeds_max_nested_depth
exceeeds_max_nested_depth,
/// Unexpects bool value.
unexpected_bool_value,
/// Expected field value is empty.
empty_field
};
namespace detail {
struct error_category_impl : std::error_category {
char const* name() const noexcept override
{ return "aedis.resp3"; }
std::string message(int ev) const override
{
switch(static_cast<error>(ev)) {
case error::invalid_type: return "Invalid resp3 type.";
case error::not_a_number: return "Can't convert string to number.";
case error::unexpected_read_size: return "Unexpected read size.";
case error::exceeeds_max_nested_depth: return "Exceeds the maximum number of nested responses.";
default: assert(false);
}
}
};
inline
std::error_category const& category()
{
static error_category_impl instance;
return instance;
}
} // detail
/** \brief Converts an error in an std::error_code object.
/** \brief Creates a error_code object from an error.
* \ingroup any
*/
inline
std::error_code make_error_code(error e)
{
static detail::error_category_impl const eci{};
return std::error_code{static_cast<int>(e), detail::category()};
}
inline
std::error_condition make_error_condition(error e)
{
return std::error_condition(static_cast<int>(e), detail::category());
}
boost::system::error_code make_error_code(error e);
} // resp3
} // aedis
@@ -78,3 +50,5 @@ template<>
struct is_error_code_enum<::aedis::resp3::error> : std::true_type {};
} // std
#endif // AEDIS_RESP3_ERROR_HPP

View File

@@ -0,0 +1,49 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#include <boost/assert.hpp>
#include <aedis/resp3/error.hpp>
namespace aedis {
namespace resp3 {
namespace detail {
struct error_category_impl : boost::system::error_category {
char const* name() const noexcept override
{
return "aedis.resp3";
}
std::string message(int ev) const override
{
switch(static_cast<error>(ev)) {
case error::invalid_type: return "Invalid resp3 type.";
case error::not_a_number: return "Can't convert string to number.";
case error::unexpected_read_size: return "Unexpected read size.";
case error::exceeeds_max_nested_depth: return "Exceeds the maximum number of nested responses.";
case error::unexpected_bool_value: return "Unexpected bool value.";
case error::empty_field: return "Expected field value is empty.";
default: BOOST_ASSERT(false);
}
}
};
boost::system::error_category const& category()
{
static error_category_impl instance;
return instance;
}
} // detail
boost::system::error_code make_error_code(error e)
{
return boost::system::error_code{static_cast<int>(e), detail::category()};
}
} // resp3
} // aedis

View File

@@ -1,67 +0,0 @@
/* Copyright (c) 2019 - 2022 Marcelo Zimbres Silva (mzimbres at gmail dot com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <aedis/resp3/node.hpp>
namespace aedis {
namespace resp3 {
std::string to_string(node const& in)
{
std::string out;
out += std::to_string(in.depth);
out += '\t';
out += to_string(in.data_type);
out += '\t';
out += std::to_string(in.aggregate_size);
out += '\t';
if (!is_aggregate(in.data_type))
out += in.data;
return out;
}
bool operator==(node const& a, node const& b)
{
return a.aggregate_size == b.aggregate_size
&& a.depth == b.depth
&& a.data_type == b.data_type
&& a.data == b.data;
};
std::ostream& operator<<(std::ostream& os, node const& o)
{
os << to_string(o);
return os;
}
std::ostream& operator<<(std::ostream& os, std::vector<node> const& r)
{
os << to_string(r);
return os;
}
// TODO: Output like in redis-cli.
std::string to_string(std::vector<node> const& vec)
{
if (std::empty(vec))
return {};
auto begin = std::cbegin(vec);
std::string res;
for (; begin != std::prev(std::cend(vec)); ++begin) {
res += to_string(*begin);
res += '\n';
}
res += to_string(*begin);
return res;
}
} // resp3
} // aedis

View File

@@ -1,14 +1,12 @@
/* Copyright (c) 2019 - 2021 Marcelo Zimbres Silva (mzimbres at gmail dot com)
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#include <boost/assert.hpp>
#include <aedis/resp3/type.hpp>
#include <cassert>
namespace aedis {
namespace resp3 {
@@ -64,5 +62,54 @@ std::size_t element_multiplicity(type t)
}
}
char to_code(type t)
{
switch (t) {
case type::blob_error: return '!';
case type::verbatim_string: return '=';
case type::blob_string: return '$';
case type::streamed_string_part: return ';';
case type::simple_error: return '-';
case type::number: return ':';
case type::doublean: return ',';
case type::boolean: return '#';
case type::big_number: return '(';
case type::simple_string: return '+';
case type::null: return '_';
case type::push: return '>';
case type::set: return '~';
case type::array: return '*';
case type::attribute: return '|';
case type::map: return '%';
default:
BOOST_ASSERT(false);
return ' ';
}
}
type to_type(char c)
{
switch (c) {
case '!': return type::blob_error;
case '=': return type::verbatim_string;
case '$': return type::blob_string;
case ';': return type::streamed_string_part;
case '-': return type::simple_error;
case ':': return type::number;
case ',': return type::doublean;
case '#': return type::boolean;
case '(': return type::big_number;
case '+': return type::simple_string;
case '_': return type::null;
case '>': return type::push;
case '~': return type::set;
case '*': return type::array;
case '|': return type::attribute;
case '%': return type::map;
default: return type::invalid;
}
}
} // resp3
} // aedis

View File

@@ -1,11 +1,11 @@
/* Copyright (c) 2019 - 2022 Marcelo Zimbres Silva (mzimbres at gmail dot com)
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#pragma once
#ifndef AEDIS_RESP3_NODE_HPP
#define AEDIS_RESP3_NODE_HPP
#include <aedis/resp3/type.hpp>
@@ -20,10 +20,14 @@ namespace resp3 {
*
* Redis responses are the pre-order view of the response tree (see
* https://en.wikipedia.org/wiki/Tree_traversal#Pre-order,_NLR).
*
* \remark Any Redis response can be received in an array of nodes,
* for example \c std::vector<node<std::string>>.
*/
template <class String>
struct node {
/// The RESP3 type of the data in this node.
type data_type;
resp3::type data_type;
/// The number of elements of an aggregate.
std::size_t aggregate_size;
@@ -31,38 +35,56 @@ struct node {
/// The depth of this node in the response tree.
std::size_t depth;
/// The actual data. For aggregate data types this is always empty.
std::string data;
/// The actual data. For aggregate types this is usually empty.
String value;
};
/** \brief Converts the node to a string.
* \ingroup any
*
* \param obj The node object.
* \param in The node object.
*/
std::string to_string(node const& obj);
template <class String>
std::string to_string(node<String> const& in)
{
std::string out;
out += std::to_string(in.depth);
out += '\t';
out += to_string(in.data_type);
out += '\t';
out += std::to_string(in.aggregate_size);
out += '\t';
if (!is_aggregate(in.data_type))
out.append(in.value.data(), in.value.size());
return out;
}
/** \brief Compares a node for equality.
* \ingroup any
*/
bool operator==(node const& a, node const& b);
template <class String>
bool operator==(node<String> const& a, node<String> const& b)
{
return a.aggregate_size == b.aggregate_size
&& a.depth == b.depth
&& a.data_type == b.data_type
&& a.value == b.value;
};
/** \brief Writes the node to the stream.
/** \brief Writes the node string to the stream.
* \ingroup any
*
* NOTE: Binary data is not converted to text.
*/
std::ostream& operator<<(std::ostream& os, node const& o);
template <class String>
std::ostream& operator<<(std::ostream& os, node<String> const& o)
{
os << to_string(o);
return os;
}
/** \brief Writes the response to the output stream
* \ingroup any
*/
std::string to_string(std::vector<node> const& vec);
/** \brief Writes the response to the output stream
* \ingroup any
*/
std::ostream& operator<<(std::ostream& os, std::vector<node> const& r);
} // resp3
} // adapter
} // aedis
#endif // AEDIS_RESP3_NODE_HPP

View File

@@ -1,36 +1,53 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#pragma once
#ifndef AEDIS_RESP3_READ_HPP
#define AEDIS_RESP3_READ_HPP
#include <aedis/config.hpp>
#include <aedis/resp3/type.hpp>
#include <aedis/resp3/adapt.hpp>
#include <aedis/resp3/response_traits.hpp>
#include <aedis/resp3/detail/parser.hpp>
#include <aedis/resp3/detail/read_ops.hpp>
#include <boost/asio/read.hpp>
#include <boost/asio/compose.hpp>
#include <boost/asio/async_result.hpp>
#include <boost/asio/yield.hpp>
namespace aedis {
namespace resp3 {
/** \brief Read the response to a command sychronously.
* \ingroup functions
/** \brief Reads a complete response to a command sychronously.
* \ingroup any
*
* This function has to be called once for each command in the
* request until the whole request has been read.
* This function reads a complete response to a command or a
* server push synchronously. For example
*
* \param stream The stream from which to read.
* \param buf Auxiliary read buffer, usually a `std::string`.
* \param adapter The response adapter, see adapt.
* \param ec Error if any.
* \returns The number of bytes that have been consumed from the
* auxiliary buffer.
* @code
* int resp;
* std::string buffer;
* resp3::read(socket, dynamic_buffer(buffer), adapt(resp));
* @endcode
*
* For a complete example see examples/intro_sync.cpp. This function
* is implemented in terms of one or more calls to @c
* asio::read_until and @c asio::read functions, and is known as a @a
* composed @a operation. Furthermore, the implementation may read
* additional bytes from the stream that lie past the end of the
* message being read. These additional bytes are stored in the
* dynamic buffer, which must be preserved for subsequent reads.
*
* \param stream The stream from which to read e.g. a tcp socket.
* \param buf Dynamic buffer (version 2).
* \param adapter The response adapter, see more on \ref low-level-responses.
* \param ec If an error occurs, it will be assigned to this paramter.
* \returns The number of bytes that have been consumed from the dynamic buffer.
*
* \remark This function calls buf.consume() in each chunk of data
* after it has been passed to the adapter. Users must not consume
* the bytes after it returns.
*/
template <
class SyncReadStream,
@@ -44,12 +61,12 @@ read(
ResponseAdapter adapter,
boost::system::error_code& ec)
{
detail::parser p {adapter};
detail::parser<ResponseAdapter> p {adapter};
std::size_t n = 0;
std::size_t consumed = 0;
do {
if (p.bulk() == type::invalid) {
n = net::read_until(stream, buf, "\r\n", ec);
n = boost::asio::read_until(stream, buf, "\r\n", ec);
if (ec)
return 0;
@@ -58,12 +75,12 @@ read(
return 0;
}
} else {
auto const s = std::size(buf);
auto const s = buf.size();
auto const l = p.bulk_length();
if (s < (l + 2)) {
auto const to_read = l + 2 - s;
buf.grow(to_read);
n = net::read(stream, buf.data(s, to_read), ec);
n = boost::asio::read(stream, buf.data(s, to_read), ec);
if (ec)
return 0;
@@ -74,9 +91,8 @@ read(
}
}
std::error_code ec;
auto const* data = (char const*) buf.data(0, n).data();
n = p.advance(data, n, ec);
n = p.consume(data, n, ec);
if (ec)
return 0;
@@ -87,27 +103,20 @@ read(
return consumed;
}
/** \brief Reads the reponse to a command.
* \ingroup functions
/** \brief Reads a complete response to a command sychronously.
* \ingroup any
*
* This function has to be called once for each command in the
* request until the whole request has been read.
*
* \param stream The stream from which to read.
* \param buf Auxiliary read buffer, usually a `std::string`.
* \param adapter The response adapter, see adapt.
* \returns The number of bytes that have been consumed from the
* auxiliary buffer.
* Same as the error_code overload but throws on error.
*/
template<
class SyncReadStream,
class DynamicBuffer,
class ResponseAdapter = response_traits<void>::adapter_type>
class ResponseAdapter = detail::ignore_response>
std::size_t
read(
SyncReadStream& stream,
DynamicBuffer buf,
ResponseAdapter adapter = adapt())
ResponseAdapter adapter = ResponseAdapter{})
{
boost::system::error_code ec;
auto const n = resp3::read(stream, buf, adapter, ec);
@@ -118,40 +127,58 @@ read(
return n;
}
/** @brief Reads the response to a Redis command asynchronously.
* \ingroup functions
/** @brief Reads a complete response to a Redis command asynchronously.
* \ingroup any
*
* This function has to be called once for each command in the
* request until the whole request has been read.
*
* The completion handler must have the following signature.
* This function reads a complete response to a command or a
* server push asynchronously. For example
*
* @code
* void(boost::system::error_code, std::size_t)
* std::string buffer;
* std::set<std::string> resp;
* co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(resp));
* @endcode
*
* The second argumet to the completion handler is the number of
* bytes that have been consumed in the read operation.
* For a complete example see examples/transaction.cpp. This function
* is implemented in terms of one or more calls to @c
* asio::async_read_until and @c asio::async_read functions, and is
* known as a @a composed @a operation. Furthermore, the
* implementation may read additional bytes from the stream that lie
* past the end of the message being read. These additional bytes are
* stored in the dynamic buffer, which must be preserved for
* subsequent reads.
*
* \param stream The stream from which to read.
* \param buffer Auxiliary read buffer, usually a `std::string`.
* \param adapter The response adapter, see adapt.
* \param stream The stream from which to read e.g. a tcp socket.
* \param buffer Dynamic buffer (version 2).
* \param adapter The response adapter, see more on \ref low-level-responses.
* \param token The completion token.
*
* The completion handler will receive as a parameter the total
* number of bytes transferred from the stream and must have the
* following signature
*
* @code
* void(boost::system::error_code, std::size_t);
* @endcode
*
* \remark This function calls buf.consume() in each chunk of data
* after it has been passed to the adapter. Users must not consume
* the bytes after it returns.
*/
template <
class AsyncReadStream,
class DynamicBuffer,
class ResponseAdapter = response_traits<void>::adapter_type,
class CompletionToken = net::default_completion_token_t<typename AsyncReadStream::executor_type>
class ResponseAdapter = detail::ignore_response,
class CompletionToken = boost::asio::default_completion_token_t<typename AsyncReadStream::executor_type>
>
auto async_read(
AsyncReadStream& stream,
DynamicBuffer buffer,
ResponseAdapter adapter = adapt(),
ResponseAdapter adapter = ResponseAdapter{},
CompletionToken&& token =
net::default_completion_token_t<typename AsyncReadStream::executor_type>{})
boost::asio::default_completion_token_t<typename AsyncReadStream::executor_type>{})
{
return net::async_compose
return boost::asio::async_compose
< CompletionToken
, void(boost::system::error_code, std::size_t)
>(detail::parse_op<AsyncReadStream, DynamicBuffer, ResponseAdapter> {stream, buffer, adapter},
@@ -159,39 +186,9 @@ auto async_read(
stream);
}
/** \brief Reads the RESP3 type of the next incomming.
* \ingroup functions
*
* This function won't consume any data from the buffer. The
* completion handler must have the following signature.
*
* @code
void(boost::system::error_code, type)
* @endcode
*
* \param stream The stream from which to read.
* \param buffer Auxiliary read buffer, usually a `std::string`.
* \param token The completion token.
*/
template <
class AsyncReadStream,
class DynamicBuffer,
class CompletionToken =
net::default_completion_token_t<typename AsyncReadStream::executor_type>
>
auto async_read_type(
AsyncReadStream& stream,
DynamicBuffer buffer,
CompletionToken&& token =
net::default_completion_token_t<typename AsyncReadStream::executor_type>{})
{
return net::async_compose
< CompletionToken
, void(boost::system::error_code, type)
>(detail::type_op<AsyncReadStream, DynamicBuffer> {stream, buffer}, token, stream);
}
} // resp3
} // aedis
#include <boost/asio/unyield.hpp>
#endif // AEDIS_RESP3_READ_HPP

View File

@@ -1,229 +0,0 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <set>
#include <array>
#include <unordered_set>
#include <list>
#include <deque>
#include <vector>
#include <charconv>
#include <tuple>
#include <variant>
#include <boost/mp11.hpp>
#include <aedis/resp3/type.hpp>
#include <aedis/resp3/adapter/detail/adapters.hpp>
#include <aedis/resp3/adapter/error.hpp>
namespace aedis {
namespace resp3 {
/** \brief Traits class for response objects.
* \ingroup any
*/
template <class T>
struct response_traits
{
/// The response type.
using response_type = T;
/// The adapter type.
using adapter_type = adapter::detail::simple<response_type>;
/// Returns an adapter for the reponse r
static auto adapt(response_type& r) noexcept { return adapter_type{&r}; }
};
/// Template typedef for response_traits.
template <class T>
using response_traits_t = typename response_traits<T>::adapter_type;
template <class T>
struct response_traits<std::optional<T>>
{
using response_type = std::optional<T>;
using adapter_type = adapter::detail::simple_optional<typename response_type::value_type>;
static auto adapt(response_type& i) noexcept { return adapter_type{&i}; }
};
template <class T, class Allocator>
struct response_traits<std::vector<T, Allocator>>
{
using response_type = std::vector<T, Allocator>;
using adapter_type = adapter::detail::vector<response_type>;
static auto adapt(response_type& v) noexcept { return adapter_type{&v}; }
};
template <>
struct response_traits<node>
{
using response_type = node;
using adapter_type = adapter::detail::adapter_node<response_type>;
static auto adapt(response_type& v) noexcept { return adapter_type{&v}; }
};
template <class Allocator>
struct response_traits<std::vector<node, Allocator>>
{
using response_type = std::vector<node, Allocator>;
using adapter_type = adapter::detail::general<response_type>;
static auto adapt(response_type& v) noexcept { return adapter_type{&v}; }
};
template <class T, class Allocator>
struct response_traits<std::list<T, Allocator>>
{
using response_type = std::list<T, Allocator>;
using adapter_type = adapter::detail::list<response_type>;
static auto adapt(response_type& v) noexcept { return adapter_type{&v}; }
};
template <class T, class Allocator>
struct response_traits<std::deque<T, Allocator>>
{
using response_type = std::deque<T, Allocator>;
using adapter_type = adapter::detail::list<response_type>;
static auto adapt(response_type& v) noexcept { return adapter_type{&v}; }
};
template <class Key, class Compare, class Allocator>
struct response_traits<std::set<Key, Compare, Allocator>>
{
using response_type = std::set<Key, Compare, Allocator>;
using adapter_type = adapter::detail::set<response_type>;
static auto adapt(response_type& s) noexcept { return adapter_type{&s}; }
};
template <class Key, class Hash, class KeyEqual, class Allocator>
struct response_traits<std::unordered_set<Key, Hash, KeyEqual, Allocator>>
{
using response_type = std::unordered_set<Key, Hash, KeyEqual, Allocator>;
using adapter_type = adapter::detail::set<response_type>;
static auto adapt(response_type& s) noexcept { return adapter_type{&s}; }
};
template <class Key, class T, class Compare, class Allocator>
struct response_traits<std::map<Key, T, Compare, Allocator>>
{
using response_type = std::map<Key, T, Compare, Allocator>;
using adapter_type = adapter::detail::map<response_type>;
static auto adapt(response_type& s) noexcept { return adapter_type{&s}; }
};
template <class Key, class Hash, class KeyEqual, class Allocator>
struct response_traits<std::unordered_map<Key, Hash, KeyEqual, Allocator>>
{
using response_type = std::unordered_map<Key, Hash, KeyEqual, Allocator>;
using adapter_type = adapter::detail::map<response_type>;
static auto adapt(response_type& s) noexcept { return adapter_type{&s}; }
};
template <>
struct response_traits<void>
{
using response_type = void;
using adapter_type = adapter::detail::ignore;
static auto adapt() noexcept { return adapter_type{}; }
};
namespace adapter {
namespace detail {
// Duplicated here to avoid circular include dependency.
template<class T>
auto internal_adapt(T& t) noexcept
{ return response_traits<T>::adapt(t); }
template <std::size_t N>
struct assigner {
template <class T1, class T2>
static void assign(T1& dest, T2& from)
{
dest[N].template emplace<N>(internal_adapt(std::get<N>(from)));
assigner<N - 1>::assign(dest, from);
}
};
template <>
struct assigner<0> {
template <class T1, class T2>
static void assign(T1& dest, T2& from)
{
dest[0] = internal_adapt(std::get<0>(from));
}
};
template <class Tuple>
class flat_transaction_adapter {
private:
using variant_type =
boost::mp11::mp_rename<boost::mp11::mp_transform<response_traits_t, Tuple>, std::variant>;
std::size_t i_ = 0;
std::size_t aggregate_size_ = 0;
std::array<variant_type, std::tuple_size<Tuple>::value> adapters_;
public:
flat_transaction_adapter(Tuple* r)
{ assigner<std::tuple_size<Tuple>::value - 1>::assign(adapters_, *r); }
void count(type t, std::size_t aggregate_size, std::size_t depth)
{
if (depth == 1) {
if (is_aggregate(t))
aggregate_size_ = aggregate_size;
else
++i_;
return;
}
if (--aggregate_size_ == 0)
++i_;
}
void
operator()(
type t,
std::size_t aggregate_size,
std::size_t depth,
char const* data,
std::size_t size,
std::error_code& ec)
{
if (depth == 0) {
if (aggregate_size != std::tuple_size<Tuple>::value) {
ec = adapter::error::incompatible_tuple_size;
return;
}
return;
}
std::visit([&](auto& arg){arg(t, aggregate_size, depth, data, size, ec);}, adapters_[i_]);
count(t, aggregate_size, depth);
}
};
} // detail
} // adapter
// The adapter of responses to transactions, move it to its own header?
template <class... Ts>
struct response_traits<std::tuple<Ts...>>
{
using response_type = std::tuple<Ts...>;
using adapter_type = adapter::detail::flat_transaction_adapter<response_type>;
static auto adapt(response_type& r) noexcept { return adapter_type{&r}; }
};
} // resp3
} // aedis

View File

@@ -1,157 +0,0 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <aedis/resp3/detail/composer.hpp>
namespace aedis {
namespace resp3 {
/** @brief Creates a Redis request from user data.
* \ingroup any
*
* A request is composed of one or more redis commands and is
* referred to in the redis documentation as a pipeline, see
* https://redis.io/topics/pipelining.
*
* For example
*
* @code
* std::string request;
* auto sr = make_serializer(request);
* sr.push(command::hello, 3);
* sr.push(command::flushall);
* sr.push(command::ping);
* sr.push(command::incr, "key");
* sr.push(command::quit);
* co_await async_write(socket, buffer(request));
* @endcode
*
* \tparam Storage The storage type. This is currently a \c std::string.
* \tparam Command The command to serialize.
*
* \remarks Non-string types will be converted to string by using \c
* to_string, which must be made available over ADL.
*/
// NOTE: Consider adding an overload for containers.
//
// TODO: Should we detect any std::pair or tuple in the type in the parameter
// pack to calculate the header size correctly?
//
// NOTE: For some commands like hset it would be a good idea to assert
// the value type is a pair.
template <class Storage, class Command>
class serializer {
private:
Storage* request_;
public:
/** \brief Constructor
*
* \param storage Object where the serialized request will be
* stored.
*/
serializer(Storage& storage) : request_(&storage) {}
/** @brief Appends a new command to the end of the request.
*
* For example
*
* \code
* std::string request;
* auto sr = make_serializer<command>(request);
* sr.push(command::set, "key", "some string", "EX", "2");
* \endcode
*
* will add the \c set command with payload "some string" and an
* expiration of 2 seconds.
*
* \param cmd The redis command.
* \param args The arguments of the Redis command.
*
*/
template <class... Ts>
void push(Command cmd, Ts const&... args)
{
auto constexpr pack_size = sizeof...(Ts);
detail::add_header(*request_, 1 + pack_size);
detail::add_bulk(*request_, to_string(cmd));
(detail::add_bulk(*request_, args), ...);
}
/** @brief Appends a new command to the end of the request.
*
* This overload is useful for commands that require a key. For example
*
* \code{.cpp}
* std::map<std::string, std::string> map
* { {"key1", "value1"}
* , {"key2", "value2"}
* , {"key3", "value3"}
* };
*
* request req;
* req.push_range(command::hset, "key", std::cbegin(map), std::cend(map));
* \endcode
*
* \param cmd The Redis command
* \param key The key the Redis command refers to.
* \param begin Iterator to the begin of the range.
* \param end Iterator to the end of the range.
*/
template <class Key, class ForwardIterator>
void push_range(Command cmd, Key const& key, ForwardIterator begin, ForwardIterator end)
{
using value_type = typename std::iterator_traits<ForwardIterator>::value_type;
auto constexpr size = detail::value_type_size<value_type>::size;
auto const distance = std::distance(begin, end);
detail::add_header(*request_, 2 + size * distance);
detail::add_bulk(*request_, to_string(cmd));
detail::add_bulk(*request_, key);
for (; begin != end; ++begin)
detail::add_bulk(*request_, *begin);
}
/** @brief Appends a new command to the end of the request.
*
* This overload is useful for commands that don't have a key. For
* example
*
* \code
* std::set<std::string> channels
* { "channel1" , "channel2" , "channel3" }
*
* request req;
* req.push(command::subscribe, std::cbegin(channels), std::cedn(channels));
* \endcode
*
* \param cmd The Redis command
* \param begin Iterator to the begin of the range.
* \param end Iterator to the end of the range.
*/
template <class ForwardIterator>
void push_range(Command cmd, ForwardIterator begin, ForwardIterator end)
{
using value_type = typename std::iterator_traits<ForwardIterator>::value_type;
auto constexpr size = detail::value_type_size<value_type>::size;
auto const distance = std::distance(begin, end);
detail::add_header(*request_, 1 + size * distance);
detail::add_bulk(*request_, to_string(cmd));
for (; begin != end; ++begin)
detail::add_bulk(*request_, *begin);
}
};
} // resp3
} // aedis

View File

@@ -1,11 +1,11 @@
/* Copyright (c) 2019 - 2021 Marcelo Zimbres Silva (mzimbres at gmail dot com)
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#pragma once
#ifndef AEDIS_RESP3_TYPE_HPP
#define AEDIS_RESP3_TYPE_HPP
#include <ostream>
#include <vector>
@@ -14,10 +14,10 @@
namespace aedis {
namespace resp3 {
/** \brief RESP3 types
/** \brief RESP3 data types.
\ingroup any
The RESP3 full specification can be found at https://github.com/antirez/RESP3/blob/74adea588783e463c7e84793b325b088fe6edd1c/spec.md
The RESP3 specification can be found at https://github.com/redis/redis-specifications/blob/master/protocol/RESP3.md.
*/
enum class type
{ /// Aggregate
@@ -56,33 +56,34 @@ enum class type
invalid
};
/** \brief Returns the string representation of the type.
/** \brief Converts the data type to a string.
* \ingroup any
* \param t RESP3 type.
*/
char const* to_string(type t);
/** \brief Writes the type to the output stream.
* \ingroup operators
* \ingroup any
* \param os Output stream.
* \param t RESP3 type.
*/
std::ostream& operator<<(std::ostream& os, type t);
/** \brief Returns true if the data type is an aggregate.
* \ingroup any
* \param t RESP3 type.
/* Checks whether the data type is an aggregate.
*/
bool is_aggregate(type t);
/** @brief Returns the element multilicity.
* \ingroup any
* \param t RESP3 type.
*
* For type map and attribute this value is 2, all other types have
* 1.
*/
// For map and attribute data types this function returns 2. All
// other types have value 1.
std::size_t element_multiplicity(type t);
// Returns the wire code of a given type.
char to_code(type t);
// Converts a wire-format RESP3 type (char) to a resp3 type.
type to_type(char c);
} // resp3
} // aedis
#endif // AEDIS_RESP3_TYPE_HPP

View File

@@ -1,11 +1,11 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#pragma once
#ifndef AEDIS_SENTINEL_COMMAND_HPP
#define AEDIS_SENTINEL_COMMAND_HPP
#include <ostream>
@@ -15,10 +15,11 @@ namespace sentinel {
/** \brief Sentinel commands.
* \ingroup any
*
* For a full list of commands see https://redis.io/topics/sentinel
* The full list of Sentinel commands can be found at
* https://redis.io/topics/sentinel.
*
* \remark The list of commands below are read from Redis with the
* help of the command \c command.
* \remark This list was created with the help of the \c command
* command.
*/
enum class command {
/// https://redis.io/commands/acl
@@ -52,28 +53,26 @@ enum class command {
/// https://redis.io/commands/unsubscribe
unsubscribe,
/// Unknown/invalid command.
unknown
invalid,
};
/** \brief Converts a sentinel command to a string
/** \brief Converts the command to a string.
* \ingroup any
*
* \param c The command to convert.
*/
char const* to_string(command c);
/** \brief Write the text for a sentinel command name to an output stream.
* \ingroup operators
*
/** \brief Writes the command string to the stream.
* \ingroup any
* \param os Output stream.
* \param c Sentinel command
*/
std::ostream& operator<<(std::ostream& os, command c);
/** \brief Returns true for sentinel commands with push response.
* \ingroup any
*/
// Checks whether a command has push response.
bool has_push_response(command cmd);
} // sentinel
} // aedis
#endif // AEDIS_SENTINEL_COMMAND_HPP

View File

@@ -1,8 +1,7 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#include <aedis/sentinel/command.hpp>

View File

@@ -1,15 +1,13 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
// Include this file in no more than one source file in your application.
#include <aedis/redis/impl/command.ipp>
#include <aedis/sentinel/impl/command.ipp>
#include <aedis/resp3/impl/type.ipp>
#include <aedis/resp3/impl/node.ipp>
#include <aedis/redis/experimental/impl/client.ipp>
#include <aedis/resp3/detail/impl/parser.ipp>
#include <aedis/resp3/impl/error.ipp>
#include <aedis/redis/impl/command.ipp>
#include <aedis/adapter/impl/error.ipp>
#include <aedis/sentinel/impl/command.ipp>
#include <aedis/generic/impl/error.ipp>

View File

@@ -1,5 +1,5 @@
AC_PREREQ([2.69])
AC_INIT([Aedis], [0.0.1], [mzimbres@gmail.com])
AC_INIT([Aedis], [0.1.2], [mzimbres@gmail.com])
AC_CONFIG_MACRO_DIR([m4])
#AC_CONFIG_SRCDIR([src/aedis.cpp])
AC_CONFIG_HEADERS([config.h])
@@ -18,5 +18,10 @@ AC_CHECK_HEADER_STDBOOL
AC_TYPE_UINT64_T
AC_CHECK_TYPES([ptrdiff_t])
AX_CXX_COMPILE_STDCXX(14, , mandatory)
AX_CXX_COMPILE_STDCXX(20, , optional)
AM_CONDITIONAL(HAVE_CXX20,[test x$HAVE_CXX20 == x1])
AC_CONFIG_FILES([Makefile doc/Doxyfile])
AC_OUTPUT

View File

@@ -32,13 +32,13 @@ DOXYFILE_ENCODING = UTF-8
# title of most generated pages and in a few other places.
# The default value is: My Project.
PROJECT_NAME = "Aedis"
PROJECT_NAME = "@PACKAGE_NAME@"
# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
# could be handy for archiving the generated documentation or if some version
# control system is used.
PROJECT_NUMBER = "0.0.1"
PROJECT_NUMBER = "@PACKAGE_VERSION@"
# Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a
@@ -179,7 +179,7 @@ STRIP_FROM_PATH =
# specify the list of include paths that are normally passed to the compiler
# using the -I flag.
STRIP_FROM_INC_PATH =
STRIP_FROM_INC_PATH = .
# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but
# less readable) file names. This can be useful is your file systems doesn't
@@ -850,52 +850,7 @@ INPUT_ENCODING = UTF-8
# C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f, *.for, *.tcl, *.vhd,
# *.vhdl, *.ucf, *.qsf and *.ice.
FILE_PATTERNS = *.c \
*.cc \
*.cxx \
*.cpp \
*.c++ \
*.java \
*.ii \
*.ixx \
*.ipp \
*.i++ \
*.inl \
*.ddl \
*.odl \
*.h \
*.hh \
*.hxx \
*.hpp \
*.h++ \
*.cs \
*.d \
*.php \
*.php4 \
*.php5 \
*.phtml \
*.inc \
*.m \
*.markdown \
*.md \
*.mm \
*.dox \
*.doc \
*.txt \
*.py \
*.pyw \
*.f90 \
*.f95 \
*.f03 \
*.f08 \
*.f \
*.for \
*.tcl \
*.vhd \
*.vhdl \
*.ucf \
*.qsf \
*.ice
FILE_PATTERNS = *.hpp *.cpp
# The RECURSIVE tag can be used to specify whether or not subdirectories should
# be searched for input files as well.
@@ -910,7 +865,7 @@ RECURSIVE = YES
# Note that relative paths are relative to the directory from which doxygen is
# run.
EXCLUDE = include/aedis/impl include/aedis/resp3/impl include/aedis/resp3/detail
EXCLUDE =
# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
# directories that are symbolic links (a Unix file system feature) are excluded
@@ -943,7 +898,7 @@ EXCLUDE_SYMBOLS = std
# that contain example code fragments that are included (see the \include
# command).
EXAMPLE_PATH = examples
EXAMPLE_PATH =
# If the value of the EXAMPLE_PATH tag contains directories, you can use the
# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and

View File

@@ -24,9 +24,7 @@ div.contents {
padding: 15px;
}
/*
code
{
background-color:#EFD25E;
background-color:#f0e9ce;
}
*/

View File

@@ -0,0 +1,132 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#include <map>
#include <set>
#include <vector>
#include <iostream>
#include <aedis/aedis.hpp>
#include <aedis/src.hpp>
#include "print.hpp"
namespace net = boost::asio;
using aedis::resp3::node;
using aedis::adapter::adapt;
using aedis::adapter::adapter_t;
using aedis::redis::command;
using aedis::generic::client;
using client_type = client<net::ip::tcp::socket, command>;
// Response types we use in this example.
using T0 = std::vector<node<std::string>>;
using T1 = std::set<std::string>;
using T2 = std::map<std::string, std::string>;
// Some containers we will store in Redis as example.
std::vector<int> vec
{1, 2, 3, 4, 5, 6};
std::set<std::string> set
{"one", "two", "three", "four"};
std::map<std::string, std::string> map
{ {"key1", "value1"}
, {"key2", "value2"}
, {"key3", "value3"}
};
struct receiver {
public:
receiver(client_type& db)
: adapter0_{adapt(resp0_)}
, adapter1_{adapt(resp1_)}
, adapter2_{adapt(resp2_)}
, db_{&db} {}
void on_resp3(command cmd, node<boost::string_view> const& nd, boost::system::error_code& ec)
{
switch (cmd) {
case command::lrange: adapter0_(nd, ec); break;
case command::smembers: adapter1_(nd, ec); break;
case command::hgetall: adapter2_(nd, ec); break;
default:;
}
}
void on_read(command cmd, std::size_t n)
{
std::cout << "on_read: " << cmd << ", " << n << "\n";
switch (cmd) {
case command::hello:
{
db_->send_range(command::rpush, "rpush-key", vec);
db_->send_range(command::sadd, "sadd-key", set);
db_->send_range(command::hset, "hset-key", map);
} break;
case command::rpush:
db_->send(command::lrange, "rpush-key", 0, -1);
break;
case command::sadd:
db_->send(command::smembers, "sadd-key");
break;
case command::hset:
db_->send(command::hgetall, "hset-key");
db_->send(command::quit);
break;
case command::lrange:
print_and_clear_aggregate(resp0_);
break;
case command::smembers:
print_and_clear(resp1_);
break;
case command::hgetall:
print_and_clear(resp2_);
break;
default:;
}
}
void on_write(std::size_t n)
{
std::cout << "on_write: " << n << std::endl;
}
void on_push(std::size_t n) { }
private:
T0 resp0_;
T1 resp1_;
T2 resp2_;
adapter_t<T0> adapter0_;
adapter_t<T1> adapter1_;
adapter_t<T2> adapter2_;
client_type* db_;
};
int main()
{
net::io_context ioc;
client_type db{ioc.get_executor()};
auto recv = std::make_shared<receiver>(db);
db.set_receiver(recv);
db.async_run("127.0.0.1", "6379",
[](auto ec){ std::cout << ec.message() << std::endl;});
ioc.run();
}

View File

@@ -1,100 +1,89 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#include <iostream>
#include <vector>
#include <iostream>
#include <boost/asio/signal_set.hpp>
#include <aedis/aedis.hpp>
#include <aedis/src.hpp>
#include "lib/user_session.hpp"
#include "lib/net_utils.hpp"
#include "user_session.hpp"
namespace net = aedis::net;
using aedis::redis::command;
using aedis::resp3::adapt;
using aedis::resp3::experimental::client;
namespace net = boost::asio;
using aedis::resp3::node;
using aedis::resp3::type;
using aedis::adapter::adapt;
using aedis::adapter::adapter_t;
using aedis::redis::command;
using aedis::generic::client;
using aedis::user_session;
using aedis::user_session_base;
// TODO: Delete sessions that have expired.
class receiver : public std::enable_shared_from_this<receiver> {
public:
private:
std::vector<node> resps_;
std::vector<std::weak_ptr<user_session_base>> sessions_;
using client_type = client<net::ip::tcp::socket, command>;
using response_type = std::vector<node<std::string>>;
class receiver {
public:
void on_message(std::error_code ec, command cmd)
receiver(std::shared_ptr<client_type> db)
: adapter_{adapt(resp_)}
, db_{db}
{}
void on_resp3(command cmd, node<boost::string_view> const& nd, boost::system::error_code& ec)
{
if (ec) {
std::cerr << "Error: " << ec.message() << std::endl;
return;
}
switch (cmd) {
case command::incr:
{
std::cout << "Message so far: " << resps_.front().data << std::endl;
} break;
case command::unknown: // Push
{
for (auto& weak: sessions_) {
if (auto session = weak.lock()) {
session->deliver(resps_.at(3).data);
} else {
std::cout << "Session expired." << std::endl;
}
}
} break;
default: { /* Ignore */ }
}
resps_.clear();
adapter_(nd, ec);
}
auto get_extended_adapter()
void on_read(command cmd, std::size_t)
{
return [adapter = adapt(resps_)](command, type t, std::size_t aggregate_size, std::size_t depth, char const* data, std::size_t size, std::error_code& ec) mutable
{ return adapter(t, aggregate_size, depth, data, size, ec); };
switch (cmd) {
case command::hello:
db_->send(command::subscribe, "channel");
break;
case command::incr:
std::cout << "Messages so far: " << resp_.front().value << std::endl;
break;
default:;
}
resp_.clear();
}
void on_write(std::size_t n)
{
std::cout << "Number of bytes written: " << n << std::endl;
}
void on_push(std::size_t)
{
for (auto& session: sessions_)
session->deliver(resp_.at(3).value);
resp_.clear();
}
auto add(std::shared_ptr<user_session_base> session)
{ sessions_.push_back(session); }
private:
response_type resp_;
adapter_t<response_type> adapter_;
std::shared_ptr<client_type> db_;
std::vector<std::shared_ptr<user_session_base>> sessions_;
};
net::awaitable<void> connection_manager(std::shared_ptr<client> db)
net::awaitable<void>
listener(
std::shared_ptr<net::ip::tcp::acceptor> acc,
std::shared_ptr<client_type> db,
std::shared_ptr<receiver> recv)
{
try {
auto socket = co_await connect();
co_await db->engage(std::move(socket));
} catch (std::exception const& e) {
std::cerr << "Error: " << e.what() << std::endl;
}
}
net::awaitable<void> listener()
{
auto ex = co_await net::this_coro::executor;
net::ip::tcp::acceptor acceptor(ex, {net::ip::tcp::v4(), 55555});
auto recv = std::make_shared<receiver>();
auto on_db_msg = [recv](std::error_code ec, command cmd)
{ recv->on_message(ec, cmd); };
auto db = std::make_shared<client>(ex);
db->set_extended_adapter(recv->get_extended_adapter());
db->set_msg_callback(on_db_msg);
net::co_spawn(ex, connection_manager(db), net::detached);
db->send(command::subscribe, "channel");
auto on_user_msg = [db](std::string const& msg)
{
db->send(command::publish, "channel", msg);
@@ -102,10 +91,10 @@ net::awaitable<void> listener()
};
for (;;) {
auto socket = co_await acceptor.async_accept(net::use_awaitable);
auto socket = co_await acc->async_accept(net::use_awaitable);
auto session = std::make_shared<user_session>(std::move(socket));
recv->add(session);
session->start(on_user_msg);
recv->add(session);
}
}
@@ -113,9 +102,21 @@ int main()
{
try {
net::io_context ioc{1};
net::signal_set signals(ioc, SIGINT, SIGTERM);
signals.async_wait([&](auto, auto){ ioc.stop(); });
co_spawn(ioc, listener(), net::detached);
auto db = std::make_shared<client_type>(ioc.get_executor());
auto recv = std::make_shared<receiver>(db);
db->set_receiver(recv);
db->async_run("127.0.0.1", "6379",
[](auto ec){ std::cout << ec.message() << std::endl;});
auto endpoint = net::ip::tcp::endpoint{net::ip::tcp::v4(), 55555};
auto acc = std::make_shared<net::ip::tcp::acceptor>(ioc.get_executor(), endpoint);
co_spawn(ioc, listener(acc, db, recv), net::detached);
net::signal_set signals(ioc.get_executor(), SIGINT, SIGTERM);
signals.async_wait([&] (auto, int) { ioc.stop(); });
ioc.run();
} catch (std::exception const& e) {
std::cerr << e.what() << std::endl;

View File

@@ -0,0 +1,63 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#include <iostream>
#include <boost/asio/use_awaitable.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/co_spawn.hpp>
#include <aedis/aedis.hpp>
#include <aedis/src.hpp>
namespace net = boost::asio;
namespace resp3 = aedis::resp3;
using aedis::resp3::node;
using aedis::redis::command;
using aedis::generic::make_serializer;
using net::ip::tcp;
using tcp_socket = net::use_awaitable_t<>::as_default_on_t<net::ip::tcp::socket>;
net::awaitable<void> example()
{
auto ex = co_await net::this_coro::executor;
tcp::resolver resv{ex};
auto const res = resv.resolve("127.0.0.1", "6379");
tcp_socket socket{ex};
co_await socket.async_connect(*std::begin(res));
std::string request, buffer;
auto sr = make_serializer(request);
sr.push(command::hello, 3);
sr.push(command::ping, "Some message.");
sr.push(command::quit);
co_await net::async_write(socket, net::buffer(request));
auto adapter = [](node<boost::string_view> const& nd, boost::system::error_code&)
{
std::cout << nd << std::endl;
};
auto dbuffer = net::dynamic_buffer(buffer);
co_await resp3::async_read(socket, dbuffer); // hello
co_await resp3::async_read(socket, dbuffer, adapter);
co_await resp3::async_read(socket, dbuffer); // quit
}
int main()
{
try {
net::io_context ioc;
net::co_spawn(ioc, example(), net::detached);
ioc.run();
} catch (std::exception const& e) {
std::cerr << e.what() << std::endl;
}
}

View File

@@ -1,106 +1,116 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#include <iostream>
#include <queue>
#include <vector>
#include <string>
#include <boost/asio/signal_set.hpp>
#include <aedis/aedis.hpp>
#include <aedis/src.hpp>
#include "lib/user_session.hpp"
#include "lib/net_utils.hpp"
#include "user_session.hpp"
namespace net = aedis::net;
namespace net = boost::asio;
using aedis::resp3::node;
using aedis::adapter::adapt;
using aedis::adapter::adapter_t;
using aedis::redis::command;
using aedis::generic::client;
using aedis::user_session;
using aedis::user_session_base;
using aedis::resp3::node;
using aedis::resp3::adapt;
using aedis::resp3::experimental::client;
using aedis::resp3::type;
class receiver : public std::enable_shared_from_this<receiver> {
private:
std::vector<node> resps_;
std::queue<std::weak_ptr<user_session_base>> sessions_;
using client_type = client<net::ip::tcp::socket, command>;
using response_type = std::vector<node<std::string>>;
class receiver {
public:
void on_message(std::error_code ec, command cmd)
receiver(std::shared_ptr<client_type> db)
: adapter_{adapt(resp_)}
, db_{db}
{}
void on_resp3(command cmd, node<boost::string_view> const& nd, boost::system::error_code& ec)
{
if (ec) {
std::cerr << "Error: " << ec.message() << std::endl;
return;
}
adapter_(nd, ec);
}
void on_read(command cmd, std::size_t n)
{
std::cout << "on_read: " << cmd << " " << n << std::endl;
switch (cmd) {
case command::ping:
{
if (auto session = sessions_.front().lock()) {
session->deliver(resps_.front().data);
} else {
std::cout << "Session expired." << std::endl;
}
if (resp_.front().value != "PONG") {
sessions_.front()->deliver(resp_.front().value);
sessions_.pop();
} break;
}
break;
case command::incr:
{
std::cout << "Echos so far: " << resps_.front().data << std::endl;
} break;
default: { /* Ignore */; }
std::cout << "Echos so far: " << resp_.front().value << std::endl;
break;
default: /* Ignore */;
}
resps_.clear();
resp_.clear();
}
auto get_extended_adapter()
{
return [adapter = adapt(resps_)](command, type t, std::size_t aggregate_size, std::size_t depth, char const* data, std::size_t size, std::error_code& ec) mutable
{ return adapter(t, aggregate_size, depth, data, size, ec); };
void on_write(std::size_t n)
{
std::cout << "Number of bytes written: " << n << std::endl;
}
void on_push(std::size_t n) { }
void add_user_session(std::shared_ptr<user_session_base> session)
{ sessions_.push(session); }
private:
response_type resp_;
adapter_t<response_type> adapter_;
std::shared_ptr<client_type> db_;
std::queue<std::shared_ptr<user_session_base>> sessions_;
};
net::awaitable<void> connection_manager(std::shared_ptr<client> db)
net::awaitable<void>
run_with_reconnect(std::shared_ptr<client_type> db)
{
try {
auto socket = co_await connect();
co_await db->engage(std::move(socket));
} catch (std::exception const& e) {
std::cerr << "Error: " << e.what() << std::endl;
auto ex = co_await net::this_coro::executor;
boost::asio::steady_timer timer{ex};
for (boost::system::error_code ec;;) {
co_await db->async_run("127.0.0.1", "6379",
net::redirect_error(net::use_awaitable, ec));
timer.expires_after(std::chrono::seconds{2});
co_await timer.async_wait(net::redirect_error(net::use_awaitable, ec));
}
}
net::awaitable<void> listener()
net::awaitable<void>
listener(
std::shared_ptr<net::ip::tcp::acceptor> acc,
std::shared_ptr<client_type> db,
std::shared_ptr<receiver> recv)
{
auto ex = co_await net::this_coro::executor;
net::ip::tcp::acceptor acceptor(ex, {net::ip::tcp::v4(), 55555});
auto recv = std::make_shared<receiver>();
auto on_db_msg = [recv](std::error_code ec, command cmd)
{ recv->on_message(ec, cmd); };
auto db = std::make_shared<client>(ex);
db->set_extended_adapter(recv->get_extended_adapter());
db->set_msg_callback(on_db_msg);
net::co_spawn(ex, connection_manager(db), net::detached);
for (;;) {
auto socket = co_await acceptor.async_accept(net::use_awaitable);
auto socket = co_await acc->async_accept(net::use_awaitable);
auto session = std::make_shared<user_session>(std::move(socket));
auto on_user_msg = [db, recv, session](std::string const& msg)
{
db->send(command::ping, msg);
db->send(command::incr, "echo-counter");
recv->add_user_session(session);
if (!msg.empty()) {
db->send(command::ping, msg);
db->send(command::incr, "echo-counter");
recv->add_user_session(session);
}
};
session->start(on_user_msg);
@@ -110,10 +120,20 @@ net::awaitable<void> listener()
int main()
{
try {
net::io_context ioc{1};
net::signal_set signals(ioc, SIGINT, SIGTERM);
signals.async_wait([&](auto, auto){ ioc.stop(); });
co_spawn(ioc, listener(), net::detached);
net::io_context ioc;
auto db = std::make_shared<client_type>(ioc.get_executor());
auto recv = std::make_shared<receiver>(db);
db->set_receiver(recv);
co_spawn(ioc, run_with_reconnect(db), net::detached);
auto endpoint = net::ip::tcp::endpoint{net::ip::tcp::v4(), 55555};
auto acc = std::make_shared<net::ip::tcp::acceptor>(ioc.get_executor(), endpoint);
co_spawn(ioc, listener(acc, db, recv), net::detached);
net::signal_set signals(ioc.get_executor(), SIGINT, SIGTERM);
signals.async_wait([&] (auto, int) { ioc.stop(); });
ioc.run();
} catch (std::exception const& e) {
std::cerr << e.what() << std::endl;

View File

@@ -1,88 +0,0 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include <iostream>
#include <vector>
#include <map>
#include <unordered_map>
#include <aedis/aedis.hpp>
#include <aedis/src.hpp>
#include "lib/net_utils.hpp"
namespace resp3 = aedis::resp3;
using aedis::redis::command;
using aedis::redis::make_serializer;
using resp3::adapt;
namespace net = aedis::net;
using net::async_write;
using net::buffer;
using net::dynamic_buffer;
net::awaitable<void> containers()
{
try {
auto socket = co_await connect();
std::map<std::string, std::string> map
{ {"key1", "value1"}
, {"key2", "value2"}
, {"key3", "value3"}
};
// Creates and sends the request.
std::string request;
auto sr = make_serializer(request);
sr.push(command::hello, 3);
sr.push(command::flushall);
sr.push_range(command::hset, "key", std::cbegin(map), std::cend(map));
sr.push(command::hgetall, "key");
sr.push(command::hgetall, "key");
sr.push(command::hgetall, "key");
sr.push(command::quit);
co_await async_write(socket, buffer(request));
// The expected responses
int hset;
std::vector<std::string> hgetall1;
std::map<std::string, std::string> hgetall2;
std::unordered_map<std::string, std::string> hgetall3;
// Reads the responses.
std::string buffer;
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // hello
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // flushall
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(hset));
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(hgetall1));
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(hgetall2));
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(hgetall3));
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // quit
// Prints the responses.
std::cout << "hset: " << hset;
std::cout << "\nhgetall (as vector): ";
for (auto const& e: hgetall1) std::cout << e << ", ";
std::cout << "\nhgetall (as map): ";
for (auto const& e: hgetall2) std::cout << e.first << " ==> " << e.second << "; ";
std::cout << "\nhgetall (as unordered_map): ";
for (auto const& e: hgetall3) std::cout << e.first << " ==> " << e.second << "; ";
std::cout << "\n";
} catch (std::exception const& e) {
std::cerr << e.what() << std::endl;
exit(EXIT_FAILURE);
}
}
int main()
{
net::io_context ioc;
co_spawn(ioc, containers(), net::detached);
ioc.run();
}

View File

@@ -1,68 +0,0 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include <iostream>
#include <aedis/aedis.hpp>
#include <aedis/src.hpp>
#include "lib/net_utils.hpp"
namespace resp3 = aedis::resp3;
using aedis::redis::command;
using aedis::redis::make_serializer;
using resp3::adapt;
namespace net = aedis::net;
using net::async_write;
using net::buffer;
using net::dynamic_buffer;
net::awaitable<void> ping()
{
try {
auto socket = co_await connect(); // See lib/net_utils.hpp
// Creates and sends the request.
std::string request;
auto sr = make_serializer(request);
sr.push(command::hello, 3);
sr.push(command::flushall);
sr.push(command::ping);
sr.push(command::incr, "key");
sr.push(command::quit);
co_await async_write(socket, buffer(request));
// Responses we are interested in.
int incr;
std::string ping;
// Reads the responses to ping and incr, ignore the others.
std::string buffer;
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // hello
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // flushall
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(ping));
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(incr));
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // quit
// Print the responses.
std::cout
<< "ping: " << ping << "\n"
<< "incr: " << incr << "\n";
} catch (std::exception const& e) {
std::cerr << e.what() << std::endl;
exit(EXIT_FAILURE);
}
}
int main()
{
net::io_context ioc;
co_spawn(ioc, ping(), net::detached);
ioc.run();
}

View File

@@ -0,0 +1,76 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#include <string>
#include <iostream>
#include <aedis/aedis.hpp>
#include <aedis/src.hpp>
namespace net = boost::asio;
using aedis::resp3::node;
using aedis::adapter::adapter_t;
using aedis::adapter::adapt;
using aedis::redis::command;
using aedis::generic::client;
using client_type = client<net::ip::tcp::socket, command>;
using response_type = node<std::string>;
struct receiver {
public:
receiver(client_type& db)
: adapter_{adapt(resp_)}
, db_{&db} {}
void on_resp3(command cmd, node<boost::string_view> const& nd, boost::system::error_code& ec)
{
adapter_(nd, ec);
}
void on_read(command cmd, std::size_t)
{
switch (cmd) {
case command::hello:
db_->send(command::ping, "O rato roeu a roupa do rei de Roma");
db_->send(command::incr, "intro-counter");
db_->send(command::set, "intro-key", "Três pratos de trigo para três tigres");
db_->send(command::get, "intro-key");
db_->send(command::quit);
break;
default:
std::cout << resp_.value << std::endl;
}
}
void on_write(std::size_t n)
{
std::cout << "Number of bytes written: " << n << std::endl;
}
void on_push(std::size_t n) { }
private:
response_type resp_;
adapter_t<response_type> adapter_;
client_type* db_;
};
int main()
{
net::io_context ioc;
client_type db(ioc.get_executor());
auto recv = std::make_shared<receiver>(db);
db.set_receiver(recv);
db.async_run("127.0.0.1", "6379",
[](auto ec){ std::cout << ec.message() << std::endl;});
ioc.run();
}

View File

@@ -1,22 +1,23 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#include <iostream>
#include <string>
#include <iostream>
#include <boost/asio/connect.hpp>
#include <aedis/aedis.hpp>
#include <aedis/src.hpp>
namespace net = boost::asio;
namespace resp3 = aedis::resp3;
using aedis::redis::command;
using aedis::redis::make_serializer;
using aedis::resp3::adapt;
namespace net = aedis::net;
using aedis::redis::command;
using aedis::generic::make_serializer;
using aedis::adapter::adapt;
using net::dynamic_buffer;
using net::ip::tcp;
@@ -27,24 +28,25 @@ int main()
tcp::resolver resv{ioc};
auto const res = resv.resolve("127.0.0.1", "6379");
tcp::socket socket{ioc};
connect(socket, res);
net::connect(socket, res);
// Creates and sends the request to redis.
std::string request;
auto sr = make_serializer(request);
// Creates the request and writes to the socket.
std::string buffer;
auto sr = make_serializer(buffer);
sr.push(command::hello, 3);
sr.push(command::ping);
sr.push(command::quit);
net::write(socket, net::buffer(request));
net::write(socket, net::buffer(buffer));
buffer.clear();
// Will store the response to ping.
// Responses
std::string resp;
// Reads the responses to all commands in the request.
std::string buffer;
resp3::read(socket, dynamic_buffer(buffer)); // hello (ignored)
resp3::read(socket, dynamic_buffer(buffer), adapt(resp));
resp3::read(socket, dynamic_buffer(buffer)); // quit (ignored)
auto dbuffer = dynamic_buffer(buffer);
resp3::read(socket, dbuffer);
resp3::read(socket, dbuffer, adapt(resp));
resp3::read(socket, dbuffer);
std::cout << "Ping: " << resp << std::endl;

View File

@@ -1,93 +0,0 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include <iostream>
#include <chrono>
#include <optional>
#include <aedis/src.hpp>
#include <aedis/aedis.hpp>
#include "lib/net_utils.hpp"
namespace resp3 = aedis::resp3;
using aedis::redis::command;
using aedis::redis::make_serializer;
using resp3::adapt;
using resp3::node;
namespace net = aedis::net;
using net::async_write;
using net::buffer;
using net::dynamic_buffer;
net::awaitable<void> key_expiration()
{
try {
auto socket = co_await connect();
// Creates and sends the first request.
std::string request;
auto sr = make_serializer(request);
sr.push(command::hello, 3);
sr.push(command::flushall);
sr.push(command::set, "key", "Some payload", "EX", "2");
sr.push(command::get, "key");
co_await async_write(socket, buffer(request));
// Will hold the response to get.
std::optional<std::string> get;
// Reads the responses.
std::string rbuffer;
co_await resp3::async_read(socket, dynamic_buffer(rbuffer)); // hello
co_await resp3::async_read(socket, dynamic_buffer(rbuffer)); // flushall
co_await resp3::async_read(socket, dynamic_buffer(rbuffer)); // set
co_await resp3::async_read(socket, dynamic_buffer(rbuffer), adapt(get));
std::cout
<< "Before expiration: " << get.has_value() << ", "
<< *get << std::endl;
// Waits some seconds for the key to expire.
timer tm{socket.get_executor(), std::chrono::seconds{3}};
co_await tm.async_wait();
// Creates a request to get after expiration.
get.reset(); request.clear();
sr.push(command::get, "key");
sr.push(command::get, "key");
sr.push(command::quit);
co_await async_write(socket, buffer(request));
// Reads the response to the second request.
co_await resp3::async_read(socket, dynamic_buffer(rbuffer), adapt(get));
// Reading without an optional will result in an error.
std::string str;
boost::system::error_code ec;
co_await resp3::async_read(socket, dynamic_buffer(rbuffer),
adapt(str), net::redirect_error(net::use_awaitable, ec));
// Quit
co_await resp3::async_read(socket, dynamic_buffer(rbuffer));
std::cout << "After expiration (optional): " << get.has_value() << "\n";
std::cout << "After expiration (non-optional): " << ec.message() << "\n";
} catch (std::exception const& e) {
std::cerr << e.what() << std::endl;
exit(EXIT_FAILURE);
}
}
int main()
{
net::io_context ioc;
co_spawn(ioc, key_expiration(), net::detached);
ioc.run();
}

View File

@@ -1,51 +0,0 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include <aedis/config.hpp>
using tcp_socket = aedis::net::use_awaitable_t<>::as_default_on_t<aedis::net::ip::tcp::socket>;
using tcp_resolver = aedis::net::use_awaitable_t<>::as_default_on_t<aedis::net::ip::tcp::resolver>;
using timer = aedis::net::use_awaitable_t<>::as_default_on_t<aedis::net::steady_timer>;
aedis::net::awaitable<tcp_socket>
connect(
std::string host = "127.0.0.1",
std::string port = "6379")
{
auto ex = co_await aedis::net::this_coro::executor;
tcp_resolver resolver{ex};
auto const res = co_await resolver.async_resolve(host, port);
tcp_socket socket{ex};
co_await aedis::net::async_connect(socket, res);
co_return std::move(socket);
}
//net::awaitable<void>
//client::connection_manager()
//{
// using namespace aedis::net::experimental::awaitable_operators;
// using tcp_resolver = aedis::net::use_awaitable_t<>::as_default_on_t<aedis::net::ip::tcp::resolver>;
//
// for (;;) {
// tcp_resolver resolver{socket_.get_executor()};
// auto const res = co_await resolver.async_resolve("127.0.0.1", "6379");
// co_await net::async_connect(socket_, res);
//
// co_await say_hello();
//
// timer_.expires_at(std::chrono::steady_clock::time_point::max());
// co_await (reader() && writer());
//
// socket_.close();
// timer_.cancel();
//
// timer_.expires_after(std::chrono::seconds{1});
// boost::system::error_code ec;
// co_await timer_.async_wait(net::redirect_error(net::use_awaitable, ec));
// }
//}

View File

@@ -1,89 +0,0 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include <iostream>
#include <string>
#include <vector>
#include <list>
#include <deque>
#include <aedis/aedis.hpp>
#include <aedis/src.hpp>
#include "lib/net_utils.hpp"
namespace resp3 = aedis::resp3;
using aedis::redis::command;
using aedis::redis::make_serializer;
using resp3::adapt;
namespace net = aedis::net;
using net::async_write;
using net::buffer;
using net::dynamic_buffer;
net::awaitable<void> ping()
{
try {
auto socket = co_await connect();
// Creates and sends the request.
auto vec = {1, 2, 3, 4, 5, 6};
std::string request;
auto sr = make_serializer(request);
sr.push(command::hello, 3);
sr.push(command::flushall);
sr.push_range(command::rpush, "key", std::cbegin(vec), std::cend(vec));
sr.push(command::lrange, "key", 0, -1);
sr.push(command::lrange, "key", 0, -1);
sr.push(command::lrange, "key", 0, -1);
sr.push(command::lrange, "key", 0, -1);
sr.push(command::quit);
co_await async_write(socket, buffer(request));
// Expected responses.
int rpush;
std::vector<std::string> svec;
std::list<std::string> slist;
std::deque<std::string> sdeq;
std::vector<int> ivec;
// Reads the responses.
std::string rbuffer;
co_await resp3::async_read(socket, dynamic_buffer(rbuffer)); // hello
co_await resp3::async_read(socket, dynamic_buffer(rbuffer)); // flushall
co_await resp3::async_read(socket, dynamic_buffer(rbuffer), adapt(rpush)); // rpush
co_await resp3::async_read(socket, dynamic_buffer(rbuffer), adapt(svec));
co_await resp3::async_read(socket, dynamic_buffer(rbuffer), adapt(slist));
co_await resp3::async_read(socket, dynamic_buffer(rbuffer), adapt(sdeq));
co_await resp3::async_read(socket, dynamic_buffer(rbuffer), adapt(ivec));
co_await resp3::async_read(socket, dynamic_buffer(rbuffer)); // quit
// Prints the responses.
std::cout << "rpush: " << rpush;
std::cout << "\nlrange (as vector): ";
for (auto e: svec) std::cout << e << " ";
std::cout << "\nlrange (as list): ";
for (auto e: slist) std::cout << e << " ";
std::cout << "\nlrange (as deque): ";
for (auto e: sdeq) std::cout << e << " ";
std::cout << "\nlrange (as vector<int>): ";
for (auto e: ivec) std::cout << e << " ";
std::cout << std::endl;
} catch (std::exception const& e) {
std::cerr << e.what() << std::endl;
exit(EXIT_FAILURE);
}
}
int main()
{
net::io_context ioc;
co_spawn(ioc, ping(), net::detached);
ioc.run();
}

53
examples/mystruct.hpp Normal file
View File

@@ -0,0 +1,53 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#include <string>
#include <iterator>
#include <cstdint>
#include <iostream>
#include <algorithm>
#include <aedis/aedis.hpp>
// Arbitrary struct to de/serialize.
struct mystruct {
std::int32_t x;
std::string y;
};
// Serializes mystruct
void to_bulk(std::string& to, mystruct const& obj)
{
using aedis::resp3::type;
using aedis::resp3::add_header;
using aedis::resp3::add_separator;
auto const size = sizeof obj.x + obj.y.size();
add_header(to, type::blob_string, size);
auto const* p = reinterpret_cast<char const*>(&obj.x);
std::copy(p, p + sizeof obj.x, std::back_inserter(to));
std::copy(std::cbegin(obj.y), std::cend(obj.y), std::back_inserter(to));
add_separator(to);
}
// Deserialize the struct.
void from_string(mystruct& obj, boost::string_view sv, boost::system::error_code& ec)
{
char* p = reinterpret_cast<char*>(&obj.x);
std::copy(std::cbegin(sv), std::cbegin(sv) + sizeof obj.x, p);
std::copy(std::cbegin(sv) + sizeof obj.x, std::cend(sv), std::back_inserter(obj.y));
}
std::ostream& operator<<(std::ostream& os, mystruct const& obj)
{
os << "x: " << obj.x << ", y: " << obj.y;
return os;
}
bool operator<(mystruct const& a, mystruct const& b)
{
return std::tie(a.x, a.y) < std::tie(b.x, b.y);
}

47
examples/print.hpp Normal file
View File

@@ -0,0 +1,47 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#include <map>
#include <set>
#include <vector>
#include <string>
#include <iostream>
#include <aedis/resp3/type.hpp>
#include <aedis/resp3/node.hpp>
// Some functions to make the examples less repetitive.
namespace net = boost::asio;
using aedis::resp3::node;
using aedis::adapter::adapt;
using aedis::adapter::adapter_t;
using aedis::redis::command;
using aedis::generic::client;
void print_and_clear_aggregate(std::vector<aedis::resp3::node<std::string>>& v)
{
auto const m = aedis::resp3::element_multiplicity(v.front().data_type);
for (auto i = 0lu; i < m * v.front().aggregate_size; ++i)
std::cout << v[i + 1].value << " ";
std::cout << "\n";
v.clear();
}
void print_and_clear(std::set<std::string>& cont)
{
for (auto const& e: cont) std::cout << e << " ";
std::cout << "\n";
cont.clear();
}
void print_and_clear(std::map<std::string, std::string>& cont)
{
for (auto const& e: cont)
std::cout << e.first << ": " << e.second << "\n";
cont.clear();
}

View File

@@ -1,82 +0,0 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres at gmail dot com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include <iostream>
#include <string_view>
#include <aedis/aedis.hpp>
#include <aedis/src.hpp>
#include "lib/net_utils.hpp"
namespace resp3 = aedis::resp3;
using aedis::redis::command;
using resp3::type;
using aedis::redis::make_serializer;
using resp3::adapt;
namespace net = aedis::net;
using net::async_write;
using net::buffer;
using net::dynamic_buffer;
// An adapter that prints the data it receives in the screen.
struct myadapter {
void operator()(
type t,
std::size_t aggregate_size,
std::size_t depth,
char const* data,
std::size_t size,
std::error_code&)
{
std::cout
<< "Type: " << t << "\n"
<< "Aggregate_size: " << aggregate_size << "\n"
<< "Depth: " << depth << "\n"
<< "Data: " << std::string_view(data, size) << "\n"
<< "----------------------" << "\n";
}
};
net::awaitable<void> adapter_example()
{
try {
auto socket = co_await connect();
auto list = {"one", "two", "three"};
// Creates and sends the request.
std::string request;
auto sr = make_serializer(request);
sr.push(command::hello, 3);
sr.push(command::flushall);
sr.push_range(command::rpush, "key", std::cbegin(list), std::cend(list));
sr.push(command::lrange, "key", 0, -1);
sr.push(command::quit);
co_await async_write(socket, buffer(request));
// Reads the responses.
std::string rbuffer;
co_await resp3::async_read(socket, dynamic_buffer(rbuffer)); // hello
co_await resp3::async_read(socket, dynamic_buffer(rbuffer)); // flushall
co_await resp3::async_read(socket, dynamic_buffer(rbuffer)); // rpush
co_await resp3::async_read(socket, dynamic_buffer(rbuffer), myadapter{}); // lrange
co_await resp3::async_read(socket, dynamic_buffer(rbuffer)); // quit
} catch (std::exception const& e) {
std::cerr << e.what() << std::endl;
exit(EXIT_FAILURE);
}
}
int main()
{
net::io_context ioc;
co_spawn(ioc, adapter_example(), net::detached);
ioc.run();
}

View File

@@ -1,109 +0,0 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include <string>
#include <iostream>
#include <charconv>
#include <aedis/aedis.hpp>
#include <aedis/src.hpp>
#include "lib/net_utils.hpp"
namespace resp3 = aedis::resp3;
using aedis::redis::make_serializer;
using aedis::redis::command;
using resp3::adapt;
namespace net = aedis::net;
using net::async_write;
using net::buffer;
using net::dynamic_buffer;
// Define the to_string and from_string functions for your own data
// types.
// The struct we would like to store in redis using our own
// serialization.
struct mydata {
int a;
int b;
};
// Serializes to Tab Separated Value (TSV).
std::string to_string(mydata const& obj)
{
return std::to_string(obj.a) + '\t' + std::to_string(obj.b);
}
// Deserializes TSV.
void
from_string(
mydata& obj,
char const* data,
std::size_t size,
std::error_code& ec)
{
auto const* end = data + size;
auto const* pos = std::find(data, end, '\t');
assert(pos != end); // Or use your own error code.
auto const res1 = std::from_chars(data, pos, obj.a);
if (res1.ec != std::errc()) {
ec = std::make_error_code(res1.ec);
return;
}
auto const res2 = std::from_chars(pos + 1, end, obj.b);
if (res2.ec != std::errc()) {
ec = std::make_error_code(res2.ec);
return;
}
}
net::awaitable<void> serialization()
{
try {
auto socket = co_await connect();
mydata obj{21, 22};
std::string request;
auto sr = make_serializer(request);
sr.push(command::hello, 3);
sr.push(command::flushall);
sr.push(command::set, "key", obj);
sr.push(command::get, "key");
sr.push(command::quit);
co_await async_write(socket, buffer(request));
// The response.
mydata get;
// Reads the responses.
std::string buffer;
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // hello
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // flushall
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // set
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(get));
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // quit
// Print the responses.
std::cout << "get: a = " << get.a << ", b = " << get.b << "\n";
} catch (std::exception const& e) {
std::cerr << e.what() << std::endl;
exit(EXIT_FAILURE);
}
}
int main()
{
net::io_context ioc;
co_spawn(ioc, serialization(), net::detached);
ioc.run();
}

View File

@@ -0,0 +1,68 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#include <string>
#include <iterator>
#include <cstdint>
#include <iostream>
#include <algorithm>
#include <boost/asio/connect.hpp>
#include <aedis/aedis.hpp>
#include <aedis/src.hpp>
#include "mystruct.hpp"
namespace net = boost::asio;
namespace resp3 = aedis::resp3;
using aedis::resp3::type;
using aedis::redis::command;
using aedis::generic::make_serializer;
using aedis::adapter::adapt;
using net::ip::tcp;
int main()
{
try {
net::io_context ioc;
tcp::resolver resv{ioc};
auto const res = resv.resolve("127.0.0.1", "6379");
tcp::socket socket{ioc};
net::connect(socket, res);
// This struct will be serialized and stored on Redis.
mystruct in{42, "Some string"};
// Creates and sends a request to redis.
std::string request;
auto sr = make_serializer(request);
sr.push(command::hello, 3);
sr.push(command::set, "key", in);
sr.push(command::get, "key");
sr.push(command::quit);
net::write(socket, net::buffer(request));
// Object to store the response.
mystruct out;
// Reads the responses to all commands in the request.
std::string buffer;
auto dbuf = net::dynamic_buffer(buffer);
resp3::read(socket, dbuf); // hello
resp3::read(socket, dbuf); // set
resp3::read(socket, dbuf, adapt(out)); // get
resp3::read(socket, dbuf); // quit
// Should be equal to what has been sent above.
std::cout << out << std::endl;
} catch (std::exception const& e) {
std::cerr << e.what() << std::endl;
exit(EXIT_FAILURE);
}
}

View File

@@ -1,85 +0,0 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres at gmail dot com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include <iostream>
#include <set>
#include <vector>
#include <unordered_map>
#include <aedis/aedis.hpp>
#include <aedis/src.hpp>
#include "lib/net_utils.hpp"
namespace resp3 = aedis::resp3;
using aedis::redis::command;
using aedis::redis::make_serializer;
using resp3::adapt;
namespace net = aedis::net;
using net::async_write;
using net::buffer;
using net::dynamic_buffer;
net::awaitable<void> containers()
{
try {
auto socket = co_await connect();
std::set<std::string> set
{"one", "two", "three", "four"};
// Creates and sends the request.
std::string request;
auto sr = make_serializer(request);
sr.push(command::hello, 3);
sr.push(command::flushall);
sr.push_range(command::sadd, "key", std::cbegin(set), std::cend(set));
sr.push(command::smembers, "key");
sr.push(command::smembers, "key");
sr.push(command::smembers, "key");
sr.push(command::quit);
co_await async_write(socket, buffer(request));
// Expected responses.
int sadd;
std::vector<std::string> smembers1;
std::set<std::string> smembers2;
std::unordered_set<std::string> smembers3;
// Reads the responses.
std::string buffer;
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // hello
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // flushall
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(sadd));
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(smembers1));
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(smembers2));
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(smembers3));
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // quit
// Prints the responses.
std::cout << "sadd: " << sadd;
std::cout << "\nsmembers (as vector): ";
for (auto const& e: smembers1) std::cout << e << " ";
std::cout << "\nsmembers (as set): ";
for (auto const& e: smembers2) std::cout << e << " ";
std::cout << "\nsmembers (as unordered_set): ";
for (auto const& e: smembers3) std::cout << e << " ";
std::cout << "\n";
} catch (std::exception const& e) {
std::cerr << e.what() << std::endl;
exit(EXIT_FAILURE);
}
}
int main()
{
net::io_context ioc;
co_spawn(ioc, containers(), net::detached);
ioc.run();
}

View File

@@ -1,86 +1,65 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres at gmail dot com)
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#include <iostream>
#include <chrono>
#include <boost/asio/use_awaitable.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/co_spawn.hpp>
#include <aedis/aedis.hpp>
#include <aedis/src.hpp>
#include "lib/net_utils.hpp"
namespace net = boost::asio;
namespace resp3 = aedis::resp3;
using aedis::resp3::node;
using aedis::redis::command;
using aedis::redis::make_serializer;
using resp3::adapt;
using resp3::node;
namespace net = aedis::net;
using net::async_write;
using aedis::adapter::adapt;
using aedis::generic::make_serializer;
using net::ip::tcp;
using net::write;
using net::buffer;
using net::dynamic_buffer;
using tcp_socket = net::use_awaitable_t<>::as_default_on_t<net::ip::tcp::socket>;
/* In this example we send a subscription to a channel and start
reading server side messages indefinitely.
Notice we store the id of the connection (attributed by the redis
server) to be able to identify it (in the logs for example).
After starting the example you can test it by sending messages with
redis-cli like this
$ redis-cli -3
127.0.0.1:6379> PUBLISH channel1 some-message
(integer) 3
127.0.0.1:6379>
The messages will then appear on the terminal you are running the
example.
*/
net::awaitable<void> subscriber()
net::awaitable<void> example()
{
try {
auto socket = co_await connect();
auto ex = co_await net::this_coro::executor;
std::string request;
auto sr = make_serializer(request);
sr.push(command::hello, "3");
sr.push(command::subscribe, "channel1", "channel2");
co_await async_write(socket, buffer(request));
tcp::resolver resv{ex};
auto const res = resv.resolve("127.0.0.1", "6379");
tcp_socket socket{ex};
co_await socket.async_connect(*std::begin(res));
std::vector<node> resp;
std::string request;
auto sr = make_serializer(request);
sr.push(command::hello, 3);
sr.push(command::subscribe, "channel1", "channel2");
co_await net::async_write(socket, buffer(request));
// Reads the response to the hello command.
std::string buffer;
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(resp));
co_await resp3::async_read(socket, dynamic_buffer(buffer));
// Ignores the response to hello.
std::string buffer;
co_await resp3::async_read(socket, net::dynamic_buffer(buffer));
// Saves the id of this connection.
auto const id = resp.at(8).data;
// Loops to receive server pushes.
for (;;) {
resp.clear();
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(resp));
std::cout
<< "Subscriber " << id << ":\n"
<< resp << std::endl;
}
} catch (std::exception const& e) {
std::cerr << e.what() << std::endl;
for (std::vector<node<std::string>> resp;;) {
co_await resp3::async_read(socket, net::dynamic_buffer(buffer), adapt(resp));
for (auto const& e: resp)
std::cout << e << std::endl;
resp.clear();
}
}
int main()
{
net::io_context ioc;
co_spawn(ioc, subscriber(), net::detached);
co_spawn(ioc, subscriber(), net::detached);
co_spawn(ioc, subscriber(), net::detached);
ioc.run();
try {
net::io_context ioc;
net::co_spawn(ioc, example(), net::detached);
ioc.run();
} catch (std::exception const& e) {
std::cerr << e.what() << std::endl;
}
}

View File

@@ -0,0 +1,93 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#include <iostream>
#include <aedis/aedis.hpp>
#include <aedis/src.hpp>
namespace net = boost::asio;
using aedis::resp3::node;
using aedis::sentinel::command;
using aedis::generic::client;
using aedis::adapter::adapt;
using client_type = client<net::ip::tcp::socket, command>;
using response_type = std::vector<node<std::string>>;
using adapter_type = aedis::adapter::adapter_t<response_type>;
/* In this example we send a subscription to a channel and start
* reading server side messages indefinitely.
*
* After starting the example you can test it by sending messages with
* redis-cli like this
*
* $ redis-cli -3
* 127.0.0.1:6379> PUBLISH channel1 some-message
* (integer) 3
* 127.0.0.1:6379>
*
* The messages will then appear on the terminal you are running the
* example.
*/
class receiver {
public:
receiver(client_type& db)
: adapter_{adapt(resp_)}
, db_{&db} {}
void on_resp3(command cmd, node<boost::string_view> const& nd, boost::system::error_code& ec)
{
adapter_(nd, ec);
}
void on_read(command cmd, std::size_t)
{
switch (cmd) {
case command::hello:
db_->send(command::subscribe, "channel1", "channel2");
break;
default:;
}
resp_.clear();
}
void on_write(std::size_t n)
{
std::cout << "Number of bytes written: " << n << std::endl;
}
void on_push(std::size_t)
{
std::cout
<< "Event: " << resp_.at(1).value << "\n"
<< "Channel: " << resp_.at(2).value << "\n"
<< "Message: " << resp_.at(3).value << "\n"
<< std::endl;
resp_.clear();
}
private:
response_type resp_;
adapter_type adapter_;
client_type* db_;
};
int main()
{
net::io_context ioc;
client_type db{ioc.get_executor()};
auto recv = std::make_shared<receiver>(db);
db.set_receiver(recv);
db.async_run("127.0.0.1", "6379",
[](auto ec){ std::cout << ec.message() << std::endl;});
ioc.run();
}

View File

@@ -1,85 +1,73 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres at gmail dot com)
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#include <iostream>
#include <vector>
#include <tuple>
#include <array>
#include <boost/asio/use_awaitable.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/co_spawn.hpp>
#include <aedis/aedis.hpp>
#include <aedis/src.hpp>
#include "lib/net_utils.hpp"
namespace net = boost::asio;
namespace resp3 = aedis::resp3;
using aedis::redis::command;
using aedis::redis::make_serializer;
using resp3::adapt;
using aedis::adapter::adapt;
using aedis::generic::make_serializer;
using net::ip::tcp;
using tcp_socket = net::use_awaitable_t<>::as_default_on_t<net::ip::tcp::socket>;
namespace net = aedis::net;
using net::async_write;
using net::buffer;
using net::dynamic_buffer;
net::awaitable<void> transaction()
net::awaitable<void> example()
{
try {
auto socket = co_await connect();
auto ex = co_await net::this_coro::executor;
auto list = {"one", "two", "three"};
tcp::resolver resv{ex};
auto const res = resv.resolve("127.0.0.1", "6379");
tcp_socket socket{ex};
co_await socket.async_connect(*std::begin(res));
std::string request;
auto sr = make_serializer(request);
sr.push(command::hello, 3);
sr.push(command::flushall);
sr.push(command::multi); // Starts a transaction
sr.push(command::ping, "Some message");
sr.push(command::incr, "incr1-key");
sr.push_range(command::rpush, "list-key", std::cbegin(list), std::cend(list));
sr.push(command::lrange, "list-key", 0, -1);
sr.push(command::incr, "incr2-key");
sr.push(command::exec); // Ends the transaction.
sr.push(command::quit);
co_await async_write(socket, buffer(request));
std::string request;
auto sr = make_serializer(request);
sr.push(command::hello, 3);
sr.push(command::multi);
sr.push(command::ping, "Some message.");
sr.push(command::set, "low-level-key", "some content", "EX", "2");
sr.push(command::exec);
sr.push(command::quit);
co_await net::async_write(socket, net::buffer(request));
// Expected responses.
std::tuple<std::string, int, int, std::vector<std::string>, int> execs;
std::tuple<std::string, boost::optional<std::string>> response;
// Reads the response.
std::string buffer;
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // hello
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // flushall
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // multi
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // ping
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // incr
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // rpush
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // lrange
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // incr
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(execs));
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // quit
std::string buffer;
auto dbuffer = net::dynamic_buffer(buffer);
co_await resp3::async_read(socket, dbuffer); // hellp
co_await resp3::async_read(socket, dbuffer); // multi
co_await resp3::async_read(socket, dbuffer); // ping
co_await resp3::async_read(socket, dbuffer); // set
co_await resp3::async_read(socket, dbuffer, adapt(response));
co_await resp3::async_read(socket, dbuffer); // quit
// Prints the response to the transaction.
std::cout << "ping: " << std::get<0>(execs) << "\n";
std::cout << "incr1: " << std::get<1>(execs) << "\n";
std::cout << "rpush: " << std::get<2>(execs) << "\n";
std::cout << "lrange: ";
for (auto const& e: std::get<3>(execs)) std::cout << e << " ";
std::cout << "\n";
std::cout << "incr2: " << std::get<4>(execs) << "\n";
std::cout
<< "Ping: " << std::get<0>(response) << "\n"
<< "Get (has_value): " << std::get<1>(response).has_value()
<< std::endl;
} catch (std::exception const& e) {
std::cerr << e.what() << std::endl;
exit(EXIT_FAILURE);
}
if (std::get<1>(response).has_value())
std::cout << "Get (value): " << std::get<1>(response).value() << std::endl;
}
int main()
{
net::io_context ioc;
co_spawn(ioc, transaction(), net::detached);
ioc.run();
try {
net::io_context ioc;
net::co_spawn(ioc, example(), net::detached);
ioc.run();
} catch (std::exception const& e) {
std::cerr << e.what() << std::endl;
}
}

View File

@@ -1,15 +1,18 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#pragma once
#ifndef AEDIS_USER_SESSION_HPP
#define AEDIS_USER_SESSION_HPP
#include <functional>
#include <aedis/config.hpp>
#include <boost/asio/awaitable.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/redirect_error.hpp>
// An example user session.
@@ -26,7 +29,7 @@ class user_session:
public user_session_base,
public std::enable_shared_from_this<user_session> {
public:
user_session(net::ip::tcp::socket socket)
user_session(boost::asio::ip::tcp::socket socket)
: socket_(std::move(socket))
, timer_(socket_.get_executor())
{ timer_.expires_at(std::chrono::steady_clock::time_point::max()); }
@@ -35,11 +38,11 @@ public:
{
co_spawn(socket_.get_executor(),
[self = shared_from_this(), on_msg]{ return self->reader(on_msg); },
net::detached);
boost::asio::detached);
co_spawn(socket_.get_executor(),
[self = shared_from_this()]{ return self->writer(); },
net::detached);
boost::asio::detached);
}
void deliver(std::string const& msg)
@@ -49,12 +52,12 @@ public:
}
private:
net::awaitable<void>
boost::asio::awaitable<void>
reader(std::function<void(std::string const&)> on_msg)
{
try {
for (std::string msg;;) {
auto const n = co_await net::async_read_until(socket_, net::dynamic_buffer(msg, 1024), "\n", net::use_awaitable);
auto const n = co_await boost::asio::async_read_until(socket_, boost::asio::dynamic_buffer(msg, 1024), "\n", boost::asio::use_awaitable);
on_msg(msg);
msg.erase(0, n);
}
@@ -63,15 +66,15 @@ private:
}
}
net::awaitable<void> writer()
boost::asio::awaitable<void> writer()
{
try {
while (socket_.is_open()) {
if (write_msgs_.empty()) {
boost::system::error_code ec;
co_await timer_.async_wait(redirect_error(net::use_awaitable, ec));
co_await timer_.async_wait(boost::asio::redirect_error(boost::asio::use_awaitable, ec));
} else {
co_await net::async_write(socket_, net::buffer(write_msgs_.front()), net::use_awaitable);
co_await boost::asio::async_write(socket_, boost::asio::buffer(write_msgs_.front()), boost::asio::use_awaitable);
write_msgs_.pop_front();
}
}
@@ -86,9 +89,10 @@ private:
timer_.cancel();
}
net::ip::tcp::socket socket_;
net::steady_timer timer_;
boost::asio::ip::tcp::socket socket_;
boost::asio::steady_timer timer_;
std::deque<std::string> write_msgs_;
};
} // aedis
#endif // AEDIS_USER_SESSION_HPP

1005
m4/ax_cxx_compile_stdcxx.m4 Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,17 +1,43 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#include <iostream>
#include <stdlib.h>
template <class T>
void check_equal(T const& a, T const& b, std::string const& msg = "")
void expect_eq(T const& a, T const& b, std::string const& msg = "")
{
if (a == b)
if (a == b) {
std::cout << "Success: " << msg << std::endl;
else
} else {
std::cout << "Error: " << msg << std::endl;
exit(EXIT_FAILURE);
}
}
template <class T>
void expect_error(boost::system::error_code a, T expected = {})
{
if (a == expected) {
if (a)
std::cout << "Success: " << a.message() << " (" << a.category().name() << ")" << std::endl;
} else {
std::cout << "Error: " << a.message() << " (" << a.category().name() << ")" << std::endl;
exit(EXIT_FAILURE);
}
}
template <class T>
void check_empty(T const& t)
{
if (t.empty()) {
//std::cout << "Success: " << std::endl;
} else {
std::cout << "Error: Not empty" << std::endl;
exit(EXIT_FAILURE);
}
}

499
tests/high_level.cpp Normal file
View File

@@ -0,0 +1,499 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#include <iostream>
#include <map>
#include <boost/asio.hpp>
#include <boost/system/errc.hpp>
#include <aedis/aedis.hpp>
#include <aedis/src.hpp>
#include "check.hpp"
namespace net = boost::asio;
namespace resp3 = aedis::resp3;
using aedis::resp3::node;
using aedis::redis::command;
using aedis::generic::make_serializer;
using aedis::adapter::adapt;
using aedis::adapter::adapter_t;
using node_type = aedis::resp3::node<std::string>;
using tcp = net::ip::tcp;
using client_type = aedis::generic::client<net::ip::tcp::socket, command>;
auto print_read = [](auto cmd, auto n)
{
std::cout << cmd << ": " << n << std::endl;
};
void test_resolve_error()
{
auto f = [](auto ec)
{
expect_error(ec, net::error::netdb_errors::host_not_found);
};
net::io_context ioc;
client_type db(ioc.get_executor());
db.async_run("Atibaia", "6379", f);
ioc.run();
}
void test_connect_error()
{
auto f = [](auto ec)
{
expect_error(ec, net::error::basic_errors::connection_refused);
};
net::io_context ioc;
client_type db(ioc.get_executor());
db.async_run("127.0.0.1", "1", f);
ioc.run();
}
struct receiver1 {
public:
receiver1(client_type& db) : db_{&db} {}
void on_read(command cmd, std::size_t)
{
// quit will be sent more than once. It doesn't matter.
db_->send(command::quit);
}
private:
client_type* db_;
};
// Test if a hello is automatically sent.
void test_hello()
{
auto f = [](auto ec)
{
expect_error(ec, net::error::misc_errors::eof);
};
net::io_context ioc;
client_type db(ioc.get_executor());
receiver1 recv{db};
db.set_read_handler([&recv](command cmd, std::size_t n){recv.on_read(cmd, n);});
db.async_run("127.0.0.1", "6379", f);
ioc.run();
}
struct receiver2 {
public:
receiver2(client_type& db) : db_{&db} {}
void on_write(std::size_t)
{
// Notice this causes a loop, but since quit stops the
// connection it is not a problem.
db_->send(command::quit);
}
private:
client_type* db_;
};
// Test if a hello is automatically sent but this time, uses on_write
// to send the quit command. Notice quit will be sent twice.
void test_hello2()
{
auto f = [](auto ec)
{
expect_error(ec, net::error::misc_errors::eof);
};
net::io_context ioc;
client_type db(ioc.get_executor());
receiver2 recv{db};
//db.set_read_handler(print_read);
db.set_write_handler([&recv](std::size_t n){recv.on_write(n);});
db.async_run("127.0.0.1", "6379", f);
ioc.run();
}
struct receiver3 {
public:
receiver3(client_type& db) : db_{&db} {}
void on_write(std::size_t)
{
// Notice this causes a loop.
db_->send(command::subscribe, "channel");
}
void on_push(std::size_t)
{
db_->send(command::quit);
}
private:
client_type* db_;
};
void test_push()
{
auto f = [](auto ec)
{
expect_error(ec, net::error::misc_errors::eof);
};
net::io_context ioc;
client_type db(ioc.get_executor());
receiver3 recv{db};
db.set_write_handler([&recv](std::size_t n){recv.on_write(n);});
db.set_push_handler([&recv](std::size_t n){recv.on_push(n);});
db.async_run("127.0.0.1", "6379", f);
ioc.run();
}
struct receiver4 {
public:
receiver4(client_type& db) : db_{&db} {}
void on_read()
{
// Notice this causes a loop.
db_->send(command::subscribe, "channel");
}
void on_push()
{
db_->send(command::quit);
}
private:
client_type* db_;
};
void test_push2()
{
auto f = [](auto ec)
{
expect_error(ec, net::error::misc_errors::eof);
};
net::io_context ioc;
client_type db(ioc.get_executor());
receiver4 recv{db};
db.set_read_handler([&recv](auto, auto){recv.on_read();});
db.set_push_handler([&recv](auto){recv.on_push();});
db.async_run("127.0.0.1", "6379", f);
ioc.run();
}
#include <boost/asio/yield.hpp>
struct receiver5 {
public:
int counter = 0;
receiver5(client_type& db)
: db_{&db}
, adapter_{adapt(counter)}
{}
void on_read(command) {}
void on_write()
{
if (counter == 0) {
// Avoid problems with previous runs.
db_->send(command::del, "receiver5-key");
db_->send(command::incr, "receiver5-key");
db_->send(command::quit);
}
if (counter == 1) {
db_->send(command::incr, "receiver5-key");
db_->send(command::quit);
}
}
void on_resp3(command cmd, node<boost::string_view> const& nd, boost::system::error_code& ec)
{
if (cmd == command::incr)
adapter_(nd, ec);
}
private:
client_type* db_;
adapter_t<int> adapter_;
};
template <class Receiver>
struct reconnect {
client_type db;
Receiver recv;
boost::asio::steady_timer timer;
net::coroutine coro;
reconnect(net::any_io_executor ex)
: db{ex}
, recv{db}
, timer{ex}
{
db.set_read_handler([this](auto cmd, auto){recv.on_read(cmd);});
db.set_write_handler([this](auto){recv.on_write();});
db.set_resp3_handler([this](auto a, auto b, auto c){recv.on_resp3(a, b, c);});
}
void on_event(boost::system::error_code ec)
{
reenter (coro) for (;;) {
yield db.async_run("127.0.0.1", "6379", [this](auto ec){ on_event(ec);});
expect_error(ec, net::error::misc_errors::eof);
expect_eq(recv.counter, 1, "Reconnect counter 1.");
yield db.async_run("127.0.0.1", "6379", [this](auto ec){ on_event(ec);});
expect_error(ec, net::error::misc_errors::eof);
expect_eq(recv.counter, 2, "Reconnect counter 2.");
yield db.async_run("127.0.0.1", "6379", [this](auto ec){ on_event(ec);});
expect_error(ec, net::error::misc_errors::eof);
expect_eq(recv.counter, 3, "Reconnect counter 3.");
return;
}
}
};
#include <boost/asio/unyield.hpp>
void test_reconnect()
{
net::io_context ioc;
reconnect<receiver5> rec{ioc.get_executor()};
rec.on_event({});
ioc.run();
}
struct receiver6 {
public:
int counter = 0;
receiver6(client_type& db)
: db_{&db}
, adapter_{adapt(counter)}
{}
void on_write() {}
void on_read(command cmd)
{
if (cmd == command::hello) {
db_->send(command::get, "receiver6-key");
if (counter == 0)
db_->send(command::del, "receiver6-key");
db_->send(command::incr, "receiver6-key");
db_->send(command::quit);
return;
}
}
void on_resp3(command cmd, node<boost::string_view> const& nd, boost::system::error_code& ec)
{
if (cmd == command::incr)
adapter_(nd, ec);
}
private:
client_type* db_;
adapter_t<int> adapter_;
};
void test_reconnect2()
{
net::io_context ioc;
reconnect<receiver6> rec{ioc.get_executor()};
rec.on_event({});
ioc.run();
}
struct receiver7 {
public:
int counter = 0;
receiver7(client_type& db)
: db_{&db}
, adapter_{adapt(counter)}
{}
void on_resp3(command cmd, node<boost::string_view> const& nd, boost::system::error_code& ec)
{
if (cmd == command::incr)
adapter_(nd, ec);
}
void on_write(std::size_t)
{
if (!std::exchange(sent_, true)) {
db_->send(command::del, "key");
db_->send(command::multi);
db_->send(command::ping, "aaa");
db_->send(command::incr, "key");
db_->send(command::ping, "bbb");
db_->send(command::discard);
db_->send(command::ping, "ccc");
db_->send(command::incr, "key");
db_->send(command::quit);
}
}
void on_read(command cmd, std::size_t)
{
}
private:
bool sent_ = false;
client_type* db_;
adapter_t<int> adapter_;
};
void test_discard()
{
auto f = [](auto ec)
{
expect_error(ec, net::error::misc_errors::eof);
};
net::io_context ioc;
client_type db(ioc.get_executor());
receiver7 recv{db};
db.set_read_handler([&recv](auto cmd, std::size_t n){recv.on_read(cmd, n);});
db.set_write_handler([&recv](std::size_t n){recv.on_write(n);});
db.set_resp3_handler([&recv](auto a, auto b, auto c){recv.on_resp3(a, b, c);});
db.async_run("127.0.0.1", "6379", f);
ioc.run();
expect_eq(recv.counter, 1, "test_discard.");
}
struct receiver8 {
public:
receiver8(client_type& db) : db_{&db} {}
void on_write(std::size_t)
{
std::cout << "on_write" << std::endl;
if (!std::exchange(sent_, true)) {
db_->send(command::del, "key");
db_->send(command::client, "PAUSE", 5000);
}
}
private:
bool sent_ = false;
client_type* db_;
};
void test_idle()
{
auto f = [](auto ec)
{
expect_error(ec, aedis::generic::error::idle_timeout);
};
net::io_context ioc;
client_type::config cfg;
cfg.resolve_timeout = std::chrono::seconds{1};
cfg.connect_timeout = std::chrono::seconds{1};
cfg.read_timeout = std::chrono::seconds{1};
cfg.write_timeout = std::chrono::seconds{1};
cfg.idle_timeout = std::chrono::seconds{2};
client_type db(ioc.get_executor(), cfg);
receiver8 recv{db};
db.set_write_handler([&recv](std::size_t n){recv.on_write(n);});
db.async_run("127.0.0.1", "6379", f);
ioc.run();
}
struct receiver9 {
public:
bool ping = false;
receiver9(client_type& db) : db_{&db} , adapter_{adapt(counter_)} {}
void on_resp3(command cmd, node<boost::string_view> const& nd, boost::system::error_code& ec)
{
if (cmd == command::incr)
adapter_(nd, ec);
}
void on_push(std::size_t) {}
void on_write(std::size_t)
{
if (!std::exchange(sent_, true))
db_->send(command::del, "key");
db_->send(command::incr, "key");
db_->send(command::subscribe, "channel");
}
void on_read(command cmd, std::size_t)
{
db_->send(command::incr, "key");
db_->send(command::subscribe, "channel");
if (counter_ == 100000) {
std::cout << "Success: counter increase." << std::endl;
db_->send(command::quit);
}
if (cmd == command::ping)
ping = true;
}
private:
bool sent_ = false;
client_type* db_;
int counter_ = 0;
adapter_t<int> adapter_;
};
void test_no_ping()
{
auto f = [](auto ec)
{
expect_error(ec, net::error::misc_errors::eof);
};
net::io_context ioc;
client_type::config cfg;
cfg.idle_timeout = std::chrono::seconds{2};
client_type db(ioc.get_executor(), cfg);
auto recv = std::make_shared<receiver9>(db);
db.set_receiver(recv);
db.async_run("127.0.0.1", "6379", f);
ioc.run();
expect_eq(recv->ping, false, "No ping received.");
}
int main()
{
test_resolve_error();
test_connect_error();
test_hello();
test_hello2();
test_push();
test_push2();
test_reconnect();
test_reconnect2();
test_discard();
test_no_ping();
// Must come last as it send a client pause.
test_idle();
}

717
tests/low_level.cpp Normal file
View File

@@ -0,0 +1,717 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#include <map>
#include <iostream>
#include <optional>
#include <boost/system/errc.hpp>
#include <boost/asio/awaitable.hpp>
#include <boost/asio/redirect_error.hpp>
#include <boost/asio/use_awaitable.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/beast/_experimental/test/stream.hpp>
#include <aedis/aedis.hpp>
#include <aedis/src.hpp>
#include "check.hpp"
#include "config.h"
namespace net = boost::asio;
namespace resp3 = aedis::resp3;
using test_stream = boost::beast::test::stream;
using aedis::adapter::adapt;
using node_type = aedis::resp3::node<std::string>;
//-------------------------------------------------------------------
template <class Result>
struct expect {
std::string in;
Result expected;
std::string name;
boost::system::error_code ec;
};
template <class Result>
void test_sync(net::any_io_executor ex, expect<Result> e)
{
std::string rbuffer;
test_stream ts {ex};
ts.append(e.in);
Result result;
boost::system::error_code ec;
resp3::read(ts, net::dynamic_buffer(rbuffer), adapt(result), ec);
expect_error(ec, e.ec);
if (e.ec)
return;
check_empty(rbuffer);
expect_eq(result, e.expected, e.name);
}
template <class Result>
class async_test: public std::enable_shared_from_this<async_test<Result>> {
private:
std::string rbuffer_;
test_stream ts_;
expect<Result> data_;
Result result_;
public:
async_test(net::any_io_executor ex, expect<Result> e)
: ts_{ex}
, data_{e}
{
ts_.append(e.in);
}
void run()
{
auto self = this->shared_from_this();
auto f = [self](auto ec, auto n)
{
expect_error(ec, self->data_.ec);
if (self->data_.ec)
return;
check_empty(self->rbuffer_);
expect_eq(self->result_, self->data_.expected, self->data_.name);
};
resp3::async_read(
ts_,
net::dynamic_buffer(rbuffer_),
adapt(result_),
f);
}
};
template <class Result>
void test_async(net::any_io_executor ex, expect<Result> e)
{
std::make_shared<async_test<Result>>(ex, e)->run();
}
void test_number(net::io_context& ioc)
{
boost::optional<int> ok;
ok = 11;
// Success
auto const in01 = expect<node_type>{":3\r\n", node_type{resp3::type::number, 1UL, 0UL, {"3"}}, "number.node (positive)"};
auto const in02 = expect<node_type>{":-3\r\n", node_type{resp3::type::number, 1UL, 0UL, {"-3"}}, "number.node (negative)"};
auto const in03 = expect<int>{":11\r\n", int{11}, "number.int"};
auto const in04 = expect<boost::optional<int>>{":11\r\n", ok, "number.optional.int"};
auto const in05 = expect<std::tuple<int>>{"*1\r\n:11\r\n", std::tuple<int>{11}, "number.tuple.int"};
auto const in06 = expect<boost::optional<int>>{"%11\r\n", boost::optional<int>{}, "number.optional.int", aedis::adapter::make_error_code(aedis::adapter::error::expects_simple_type)};
auto const in07 = expect<std::set<std::string>>{":11\r\n", std::set<std::string>{}, "number.optional.int", aedis::adapter::make_error_code(aedis::adapter::error::expects_set_type)};
auto const in08 = expect<std::unordered_set<std::string>>{":11\r\n", std::unordered_set<std::string>{}, "number.optional.int", aedis::adapter::make_error_code(aedis::adapter::error::expects_set_type)};
auto const in09 = expect<std::map<std::string, std::string>>{":11\r\n", std::map<std::string, std::string>{}, "number.optional.int", aedis::adapter::make_error_code(aedis::adapter::error::expects_map_type)};
auto const in10 = expect<std::unordered_map<std::string, std::string>>{":11\r\n", std::unordered_map<std::string, std::string>{}, "number.optional.int", aedis::adapter::make_error_code(aedis::adapter::error::expects_map_type)};
auto const in11 = expect<std::list<std::string>>{":11\r\n", std::list<std::string>{}, "number.optional.int", aedis::adapter::make_error_code(aedis::adapter::error::expects_aggregate_type)};
auto ex = ioc.get_executor();
test_sync(ex, in01);
test_sync(ex, in02);
test_sync(ex, in03);
test_sync(ex, in04);
test_sync(ex, in05);
test_sync(ex, in06);
test_sync(ex, in07);
test_sync(ex, in08);
test_sync(ex, in09);
test_sync(ex, in10);
test_sync(ex, in11);
test_async(ex, in01);
test_async(ex, in02);
test_async(ex, in03);
test_async(ex, in04);
test_async(ex, in05);
test_async(ex, in06);
test_async(ex, in07);
test_async(ex, in08);
test_async(ex, in09);
test_async(ex, in10);
test_async(ex, in11);
}
void test_bool(net::io_context& ioc)
{
boost::optional<bool> ok;
ok = true;
// Success.
auto const in08 = expect<node_type>{"#f\r\n", node_type{resp3::type::boolean, 1UL, 0UL, {"f"}}, "bool.node (false)"};
auto const in09 = expect<node_type>{"#t\r\n", node_type{resp3::type::boolean, 1UL, 0UL, {"t"}}, "bool.node (true)"};
auto const in10 = expect<bool>{"#t\r\n", bool{true}, "bool.bool (true)"};
auto const in11 = expect<bool>{"#f\r\n", bool{false}, "bool.bool (true)"};
auto const in13 = expect<boost::optional<bool>>{"#t\r\n", ok, "optional.int"};
// Error
auto const in01 = expect<boost::optional<bool>>{"#11\r\n", boost::optional<bool>{}, "bool.error", aedis::resp3::make_error_code(aedis::resp3::error::unexpected_bool_value)};
auto const in03 = expect<std::set<int>>{"#t\r\n", std::set<int>{}, "bool.error", aedis::adapter::make_error_code(aedis::adapter::error::expects_set_type)};
auto const in04 = expect<std::unordered_set<int>>{"#t\r\n", std::unordered_set<int>{}, "bool.error", aedis::adapter::make_error_code(aedis::adapter::error::expects_set_type)};
auto const in05 = expect<std::map<int, int>>{"#t\r\n", std::map<int, int>{}, "bool.error", aedis::adapter::make_error_code(aedis::adapter::error::expects_map_type)};
auto const in06 = expect<std::unordered_map<int, int>>{"#t\r\n", std::unordered_map<int, int>{}, "bool.error", aedis::adapter::make_error_code(aedis::adapter::error::expects_map_type)};
auto ex = ioc.get_executor();
test_sync(ex, in01);
test_sync(ex, in03);
test_sync(ex, in04);
test_sync(ex, in05);
test_sync(ex, in06);
test_sync(ex, in08);
test_sync(ex, in09);
test_sync(ex, in10);
test_sync(ex, in11);
test_async(ex, in01);
test_async(ex, in03);
test_async(ex, in04);
test_async(ex, in05);
test_async(ex, in06);
test_async(ex, in08);
test_async(ex, in09);
test_async(ex, in10);
test_async(ex, in11);
}
void test_streamed_string(net::io_context& ioc)
{
std::string const wire = "$?\r\n;4\r\nHell\r\n;5\r\no wor\r\n;1\r\nd\r\n;0\r\n";
std::vector<node_type> e1a
{ {aedis::resp3::type::streamed_string_part, 1, 1, "Hell"}
, {aedis::resp3::type::streamed_string_part, 1, 1, "o wor"}
, {aedis::resp3::type::streamed_string_part, 1, 1, "d"}
, {aedis::resp3::type::streamed_string_part, 1, 1, ""}
};
std::vector<node_type> e1b { {resp3::type::streamed_string_part, 1UL, 1UL, {}} };
auto const in01 = expect<std::vector<node_type>>{wire, e1a, "streamed_string.node"};
auto const in03 = expect<std::string>{wire, std::string{"Hello word"}, "streamed_string.string"};
auto const in02 = expect<std::vector<node_type>>{"$?\r\n;0\r\n", e1b, "streamed_string.node.empty"};
auto ex = ioc.get_executor();
test_sync(ex, in01);
test_sync(ex, in02);
test_sync(ex, in03);
test_async(ex, in01);
test_async(ex, in02);
test_async(ex, in03);
}
void test_push(net::io_context& ioc)
{
std::string const wire = ">4\r\n+pubsub\r\n+message\r\n+some-channel\r\n+some message\r\n";
std::vector<node_type> e1a
{ {resp3::type::push, 4UL, 0UL, {}}
, {resp3::type::simple_string, 1UL, 1UL, "pubsub"}
, {resp3::type::simple_string, 1UL, 1UL, "message"}
, {resp3::type::simple_string, 1UL, 1UL, "some-channel"}
, {resp3::type::simple_string, 1UL, 1UL, "some message"}
};
std::vector<node_type> e1b { {resp3::type::push, 0UL, 0UL, {}} };
auto const in01 = expect<std::vector<node_type>>{wire, e1a, "push.node"};
auto const in02 = expect<std::vector<node_type>>{">0\r\n", e1b, "push.node.empty"};
auto ex = ioc.get_executor();
test_sync(ex, in01);
test_sync(ex, in02);
test_async(ex, in01);
test_async(ex, in02);
}
void test_map(net::io_context& ioc)
{
using map_type = std::map<std::string, std::string>;
using mmap_type = std::multimap<std::string, std::string>;
using umap_type = std::unordered_map<std::string, std::string>;
using mumap_type = std::unordered_multimap<std::string, std::string>;
using vec_type = std::vector<std::string>;
using op_map_type = boost::optional<std::map<std::string, std::string>>;
using op_vec_type = boost::optional<std::vector<std::string>>;
using tuple_type = std::tuple<std::string, std::string, std::string, std::string, std::string, std::string, std::string, std::string>;
std::string const wire = "%4\r\n$4\r\nkey1\r\n$6\r\nvalue1\r\n$4\r\nkey2\r\n$6\r\nvalue2\r\n$4\r\nkey3\r\n$6\r\nvalue3\r\n$4\r\nkey3\r\n$6\r\nvalue3\r\n";
std::vector<node_type> expected_1a
{ {resp3::type::map, 4UL, 0UL, {}}
, {resp3::type::blob_string, 1UL, 1UL, {"key1"}}
, {resp3::type::blob_string, 1UL, 1UL, {"value1"}}
, {resp3::type::blob_string, 1UL, 1UL, {"key2"}}
, {resp3::type::blob_string, 1UL, 1UL, {"value2"}}
, {resp3::type::blob_string, 1UL, 1UL, {"key3"}}
, {resp3::type::blob_string, 1UL, 1UL, {"value3"}}
, {resp3::type::blob_string, 1UL, 1UL, {"key3"}}
, {resp3::type::blob_string, 1UL, 1UL, {"value3"}}
};
map_type expected_1b
{ {"key1", "value1"}
, {"key2", "value2"}
, {"key3", "value3"}
};
umap_type e1g
{ {"key1", "value1"}
, {"key2", "value2"}
, {"key3", "value3"}
};
mmap_type e1k
{ {"key1", "value1"}
, {"key2", "value2"}
, {"key3", "value3"}
, {"key3", "value3"}
};
mumap_type e1l
{ {"key1", "value1"}
, {"key2", "value2"}
, {"key3", "value3"}
, {"key3", "value3"}
};
std::vector<std::string> expected_1c
{ "key1", "value1"
, "key2", "value2"
, "key3", "value3"
, "key3", "value3"
};
op_map_type expected_1d;
expected_1d = expected_1b;
op_vec_type expected_1e;
expected_1e = expected_1c;
tuple_type e1f
{ std::string{"key1"}, std::string{"value1"}
, std::string{"key2"}, std::string{"value2"}
, std::string{"key3"}, std::string{"value3"}
, std::string{"key3"}, std::string{"value3"}
};
auto const in00 = expect<std::vector<node_type>>{wire, expected_1a, "map.node"};
auto const in01 = expect<map_type>{"%0\r\n", map_type{}, "map.map.empty"};
auto const in02 = expect<map_type>{wire, expected_1b, "map.map"};
auto const in03 = expect<mmap_type>{wire, e1k, "map.multimap"};
auto const in04 = expect<umap_type>{wire, e1g, "map.unordered_map"};
auto const in05 = expect<mumap_type>{wire, e1l, "map.unordered_multimap"};
auto const in06 = expect<vec_type>{wire, expected_1c, "map.vector"};
auto const in07 = expect<op_map_type>{wire, expected_1d, "map.optional.map"};
auto const in08 = expect<op_vec_type>{wire, expected_1e, "map.optional.vector"};
auto const in09 = expect<std::tuple<op_map_type>>{"*1\r\n" + wire, std::tuple<op_map_type>{expected_1d}, "map.transaction.optional.map"};
auto const in10 = expect<int>{"%11\r\n", int{}, "map.invalid.int", aedis::adapter::make_error_code(aedis::adapter::error::expects_simple_type)};
auto const in11 = expect<tuple_type>{wire, e1f, "map.tuple"};
auto ex = ioc.get_executor();
test_sync(ex, in00);
test_sync(ex, in01);
test_sync(ex, in02);
test_sync(ex, in03);
test_sync(ex, in04);
test_sync(ex, in05);
test_sync(ex, in07);
test_sync(ex, in08);
test_sync(ex, in09);
test_sync(ex, in00);
test_sync(ex, in11);
test_async(ex, in00);
test_async(ex, in01);
test_async(ex, in02);
test_async(ex, in03);
test_async(ex, in04);
test_async(ex, in05);
test_async(ex, in07);
test_async(ex, in08);
test_async(ex, in09);
test_async(ex, in00);
test_async(ex, in11);
}
void test_attribute(net::io_context& ioc)
{
char const* wire = "|1\r\n+key-popularity\r\n%2\r\n$1\r\na\r\n,0.1923\r\n$1\r\nb\r\n,0.0012\r\n";
std::vector<node_type> e1a
{ {resp3::type::attribute, 1UL, 0UL, {}}
, {resp3::type::simple_string, 1UL, 1UL, "key-popularity"}
, {resp3::type::map, 2UL, 1UL, {}}
, {resp3::type::blob_string, 1UL, 2UL, "a"}
, {resp3::type::doublean, 1UL, 2UL, "0.1923"}
, {resp3::type::blob_string, 1UL, 2UL, "b"}
, {resp3::type::doublean, 1UL, 2UL, "0.0012"}
};
std::vector<node_type> e1b;
auto const in01 = expect<std::vector<node_type>>{wire, e1a, "attribute.node"};
auto const in02 = expect<std::vector<node_type>>{"|0\r\n", e1b, "attribute.node.empty"};
auto ex = ioc.get_executor();
test_sync(ex, in01);
test_sync(ex, in02);
test_async(ex, in01);
test_async(ex, in02);
}
void test_array(net::io_context& ioc)
{
char const* wire = "*3\r\n$2\r\n11\r\n$2\r\n22\r\n$1\r\n3\r\n";
std::vector<node_type> e1a
{ {resp3::type::array, 3UL, 0UL, {}}
, {resp3::type::blob_string, 1UL, 1UL, {"11"}}
, {resp3::type::blob_string, 1UL, 1UL, {"22"}}
, {resp3::type::blob_string, 1UL, 1UL, {"3"}}
};
std::vector<int> const e1b{11, 22, 3};
std::vector<std::string> const e1c{"11", "22", "3"};
std::vector<std::string> const e1d{};
std::vector<node_type> const e1e{{resp3::type::array, 0UL, 0UL, {}}};
std::array<int, 3> const e1f{11, 22, 3};
std::list<int> const e1g{11, 22, 3};
std::deque<int> const e1h{11, 22, 3};
auto const in01 = expect<std::vector<node_type>>{wire, e1a, "array.node"};
auto const in02 = expect<std::vector<int>>{wire, e1b, "array.int"};
auto const in03 = expect<std::vector<node_type>>{"*0\r\n", e1e, "array.node.empty"};
auto const in04 = expect<std::vector<std::string>>{"*0\r\n", e1d, "array.string.empty"};
auto const in05 = expect<std::vector<std::string>>{wire, e1c, "array.string"};
auto const in06 = expect<std::array<int, 3>>{wire, e1f, "array.array"};
auto const in07 = expect<std::list<int>>{wire, e1g, "array.list"};
auto const in08 = expect<std::deque<int>>{wire, e1h, "array.deque"};
auto ex = ioc.get_executor();
test_sync(ex, in01);
test_sync(ex, in02);
test_sync(ex, in03);
test_sync(ex, in04);
test_sync(ex, in05);
test_sync(ex, in06);
test_sync(ex, in07);
test_sync(ex, in08);
test_async(ex, in01);
test_async(ex, in02);
test_async(ex, in03);
test_async(ex, in04);
test_async(ex, in05);
test_async(ex, in06);
test_async(ex, in07);
test_async(ex, in08);
}
void test_set(net::io_context& ioc)
{
using set_type = std::set<std::string>;
using mset_type = std::multiset<std::string>;
using uset_type = std::unordered_set<std::string>;
using muset_type = std::unordered_multiset<std::string>;
using vec_type = std::vector<std::string>;
using op_vec_type = boost::optional<std::vector<std::string>>;
std::string const wire = "~6\r\n+orange\r\n+apple\r\n+one\r\n+two\r\n+three\r\n+orange\r\n";
std::vector<node_type> const expected1a
{ {resp3::type::set, 6UL, 0UL, {}}
, {resp3::type::simple_string, 1UL, 1UL, {"orange"}}
, {resp3::type::simple_string, 1UL, 1UL, {"apple"}}
, {resp3::type::simple_string, 1UL, 1UL, {"one"}}
, {resp3::type::simple_string, 1UL, 1UL, {"two"}}
, {resp3::type::simple_string, 1UL, 1UL, {"three"}}
, {resp3::type::simple_string, 1UL, 1UL, {"orange"}}
};
mset_type const e1f{"apple", "one", "orange", "orange", "three", "two"};
uset_type const e1c{"apple", "one", "orange", "three", "two"};
muset_type const e1g{"apple", "one", "orange", "orange", "three", "two"};
vec_type const e1d = {"orange", "apple", "one", "two", "three", "orange"};
op_vec_type expected_1e;
expected_1e = e1d;
auto const in00 = expect<std::vector<node_type>>{wire, expected1a, "set.node"};
auto const in01 = expect<std::vector<node_type>>{"~0\r\n", std::vector<node_type>{ {resp3::type::set, 0UL, 0UL, {}} }, "set.node (empty)"};
auto const in02 = expect<set_type>{wire, set_type{"apple", "one", "orange", "three", "two"}, "set.set"};
auto const in03 = expect<mset_type>{wire, e1f, "set.multiset"};
auto const in04 = expect<vec_type>{wire, e1d, "set.vector "};
auto const in05 = expect<op_vec_type>{wire, expected_1e, "set.vector "};
auto const in06 = expect<uset_type>{wire, e1c, "set.unordered_set"};
auto const in07 = expect<muset_type>{wire, e1g, "set.unordered_multiset"};
auto const in08 = expect<std::tuple<uset_type>>{"*1\r\n" + wire, std::tuple<uset_type>{e1c}, "set.tuple"};
auto ex = ioc.get_executor();
test_sync(ex, in00);
test_sync(ex, in01);
test_sync(ex, in02);
test_sync(ex, in03);
test_sync(ex, in04);
test_sync(ex, in05);
test_sync(ex, in06);
test_sync(ex, in07);
test_sync(ex, in08);
test_async(ex, in00);
test_async(ex, in01);
test_async(ex, in02);
test_async(ex, in03);
test_async(ex, in04);
test_async(ex, in05);
test_async(ex, in06);
test_async(ex, in07);
test_async(ex, in08);
}
void test_simple_error(net::io_context& ioc)
{
auto const in01 = expect<node_type>{"-Error\r\n", node_type{resp3::type::simple_error, 1UL, 0UL, {"Error"}}, "simple_error.node"};
auto const in02 = expect<node_type>{"-\r\n", node_type{resp3::type::simple_error, 1UL, 0UL, {""}}, "simple_error.node.empty"};
auto ex = ioc.get_executor();
test_sync(ex, in01);
test_sync(ex, in02);
test_async(ex, in01);
test_async(ex, in02);
}
void test_blob_string(net::io_context& ioc)
{
std::string str(100000, 'a');
str[1000] = '\r';
str[1001] = '\n';
std::string wire;
wire += '$';
wire += std::to_string(str.size());
wire += "\r\n";
wire += str;
wire += "\r\n";
auto const in01 = expect<node_type>{"$2\r\nhh\r\n", node_type{resp3::type::blob_string, 1UL, 0UL, {"hh"}}, "blob_string.node"};
auto const in02 = expect<node_type>{"$26\r\nhhaa\aaaa\raaaaa\r\naaaaaaaaaa\r\n", node_type{resp3::type::blob_string, 1UL, 0UL, {"hhaa\aaaa\raaaaa\r\naaaaaaaaaa"}}, "blob_string.node (with separator)"};
auto const in03 = expect<node_type>{"$0\r\n\r\n", node_type{resp3::type::blob_string, 1UL, 0UL, {}}, "blob_string.node.empty"};
auto const in04 = expect<node_type>{wire, node_type{resp3::type::blob_string, 1UL, 0UL, {str}}, "blob_string.node (long string)"};
auto ex = ioc.get_executor();
test_sync(ex, in01);
test_sync(ex, in02);
test_sync(ex, in03);
test_sync(ex, in04);
test_async(ex, in01);
test_async(ex, in02);
test_async(ex, in03);
test_async(ex, in04);
}
void test_double(net::io_context& ioc)
{
// TODO: Add test for double.
auto const in01 = expect<node_type>{",1.23\r\n", node_type{resp3::type::doublean, 1UL, 0UL, {"1.23"}}, "double.node"};
auto const in02 = expect<node_type>{",inf\r\n", node_type{resp3::type::doublean, 1UL, 0UL, {"inf"}}, "double.node (inf)"};
auto const in03 = expect<node_type>{",-inf\r\n", node_type{resp3::type::doublean, 1UL, 0UL, {"-inf"}}, "double.node (-inf)"};
auto const in04 = expect<double>{",1.23\r\n", double{1.23}, "double.double"};
auto ex = ioc.get_executor();
test_sync(ex, in01);
test_sync(ex, in02);
test_sync(ex, in03);
test_sync(ex, in04);
test_async(ex, in01);
test_async(ex, in02);
test_async(ex, in03);
test_async(ex, in04);
}
void test_blob_error(net::io_context& ioc)
{
auto const in01 = expect<node_type>{"!21\r\nSYNTAX invalid syntax\r\n", node_type{resp3::type::blob_error, 1UL, 0UL, {"SYNTAX invalid syntax"}}, "blob_error"};
auto const in02 = expect<node_type>{"!0\r\n\r\n", node_type{resp3::type::blob_error, 1UL, 0UL, {}}, "blob_error.empty"};
auto ex = ioc.get_executor();
test_sync(ex, in01);
test_sync(ex, in02);
test_async(ex, in01);
test_async(ex, in02);
}
void test_verbatim_string(net::io_context& ioc)
{
auto const in01 = expect<node_type>{"=15\r\ntxt:Some string\r\n", node_type{resp3::type::verbatim_string, 1UL, 0UL, {"txt:Some string"}}, "verbatim_string"};
auto const in02 = expect<node_type>{"=0\r\n\r\n", node_type{resp3::type::verbatim_string, 1UL, 0UL, {}}, "verbatim_string.empty"};
auto ex = ioc.get_executor();
test_sync(ex, in01);
test_sync(ex, in02);
test_async(ex, in01);
test_async(ex, in02);
}
void test_big_number(net::io_context& ioc)
{
auto const in01 = expect<node_type>{"(3492890328409238509324850943850943825024385\r\n", node_type{resp3::type::big_number, 1UL, 0UL, {"3492890328409238509324850943850943825024385"}}, "big_number.node"};
auto const in02 = expect<int>{"(\r\n", int{}, "big_number.error (empty field)", aedis::resp3::make_error_code(aedis::resp3::error::empty_field)};
auto ex = ioc.get_executor();
test_sync(ex, in01);
test_sync(ex, in02);
test_async(ex, in01);
test_async(ex, in02);
}
void test_simple_string(net::io_context& ioc)
{
boost::optional<std::string> ok1, ok2;
ok1 = "OK";
ok2 = "";
auto in00 = expect<std::string>{"+OK\r\n", std::string{"OK"}, "simple_string.string"};
auto in01 = expect<std::string>{"+\r\n", std::string{""}, "simple_string.string.empty"};
auto in02 = expect<boost::optional<std::string>>{"+OK\r\n", boost::optional<std::string>{"OK"}, "simple_string.optional"};
auto in03 = expect<boost::optional<std::string>>{"+\r\n", boost::optional<std::string>{""}, "simple_string.optional.empty"};
auto ex = ioc.get_executor();
test_sync(ex, in00);
test_sync(ex, in01);
test_sync(ex, in02);
test_sync(ex, in03);
test_async(ex, in00);
test_async(ex, in01);
test_async(ex, in02);
test_async(ex, in03);
}
void test_resp3(net::io_context& ioc)
{
auto const in01 = expect<int>{"s11\r\n", int{}, "number.error", aedis::resp3::make_error_code(aedis::resp3::error::invalid_type)};
auto const in02 = expect<int>{":adf\r\n", int{11}, "number.int", aedis::resp3::make_error_code(aedis::resp3::error::not_a_number)};
auto const in03 = expect<int>{":\r\n", int{}, "number.error (empty field)", aedis::resp3::make_error_code(aedis::resp3::error::empty_field)};
auto const in04 = expect<boost::optional<bool>>{"#\r\n", boost::optional<bool>{}, "bool.error", aedis::resp3::make_error_code(aedis::resp3::error::empty_field)};
auto const in05 = expect<std::string>{",\r\n", std::string{}, "double.error (empty field)", aedis::resp3::make_error_code(aedis::resp3::error::empty_field)};
auto ex = ioc.get_executor();
test_sync(ex, in01);
test_sync(ex, in02);
test_sync(ex, in03);
test_sync(ex, in04);
test_sync(ex, in05);
test_async(ex, in01);
test_async(ex, in02);
test_async(ex, in03);
test_async(ex, in04);
test_async(ex, in05);
}
void test_null(net::io_context& ioc)
{
using op_type_01 = boost::optional<bool>;
using op_type_02 = boost::optional<int>;
using op_type_03 = boost::optional<std::string>;
using op_type_04 = boost::optional<std::vector<std::string>>;
using op_type_05 = boost::optional<std::list<std::string>>;
using op_type_06 = boost::optional<std::map<std::string, std::string>>;
using op_type_07 = boost::optional<std::unordered_map<std::string, std::string>>;
using op_type_08 = boost::optional<std::set<std::string>>;
using op_type_09 = boost::optional<std::unordered_set<std::string>>;
auto const in01 = expect<op_type_01>{"_\r\n", op_type_01{}, "null.optional.bool"};
auto const in02 = expect<op_type_02>{"_\r\n", op_type_02{}, "null.optional.int"};
auto const in03 = expect<op_type_03>{"_\r\n", op_type_03{}, "null.optional.string"};
auto const in04 = expect<op_type_04>{"_\r\n", op_type_04{}, "null.optional.vector"};
auto const in05 = expect<op_type_05>{"_\r\n", op_type_05{}, "null.optional.list"};
auto const in06 = expect<op_type_06>{"_\r\n", op_type_06{}, "null.optional.map"};
auto const in07 = expect<op_type_07>{"_\r\n", op_type_07{}, "null.optional.unordered_map"};
auto const in08 = expect<op_type_08>{"_\r\n", op_type_08{}, "null.optional.set"};
auto const in09 = expect<op_type_09>{"_\r\n", op_type_09{}, "null.optional.unordered_set"};
auto ex = ioc.get_executor();
test_sync(ex, in01);
test_sync(ex, in02);
test_sync(ex, in03);
test_sync(ex, in04);
test_sync(ex, in05);
test_sync(ex, in06);
test_sync(ex, in07);
test_sync(ex, in08);
test_sync(ex, in09);
test_async(ex, in01);
test_async(ex, in02);
test_async(ex, in03);
test_async(ex, in04);
test_async(ex, in05);
test_async(ex, in06);
test_async(ex, in07);
test_async(ex, in08);
test_async(ex, in09);
}
int main()
{
net::io_context ioc {1};
// Simple types.
test_simple_string(ioc);
test_simple_error(ioc);
test_blob_string(ioc);
test_blob_error(ioc);
test_number(ioc);
test_double(ioc);
test_bool(ioc);
test_null(ioc);
test_big_number(ioc);
test_verbatim_string(ioc);
// Aggregates.
test_array(ioc);
test_set(ioc);
test_map(ioc);
test_push(ioc);
test_streamed_string(ioc);
// RESP3
test_resp3(ioc);
ioc.run();
}

View File

@@ -1,648 +0,0 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include <iostream>
#include <map>
#include <aedis/src.hpp>
#include <aedis/aedis.hpp>
#include "test_stream.hpp"
#include "check.hpp"
// Consider using Beast test_stream and instantiate the test socket
// only once.
namespace net = aedis::net;
using tcp = net::ip::tcp;
using test_tcp_socket = net::use_awaitable_t<>::as_default_on_t<aedis::test_stream<aedis::net::system_executor>>;
namespace this_coro = net::this_coro;
using namespace aedis;
using namespace aedis::resp3;
std::vector<node> gresp;
//-------------------------------------------------------------------
void simple_string_sync()
{
test_tcp_socket ts {"+OK\r\n"};
std::string rbuffer;
auto dbuffer = net::dynamic_buffer(rbuffer);
{
node result;
resp3::read(ts, dbuffer, adapt(result));
node expected {resp3::type::simple_string, 1UL, 0UL, {"OK"}};
check_equal(result, expected, "simple_string (node-sync)");
}
{
std::string result;
resp3::read(ts, dbuffer, adapt(result));
std::string expected {"OK"};
check_equal(result, expected, "simple_string (string-sync)");
}
{
std::optional<std::string> result;
resp3::read(ts, dbuffer, adapt(result));
std::optional<std::string> expected {"OK"};
check_equal(result, expected, "simple_string (optional-string-sync)");
}
}
void simple_string_sync_empty()
{
test_tcp_socket ts {"+\r\n"};
std::string rbuffer;
auto dbuffer = net::dynamic_buffer(rbuffer);
{
node result;
resp3::read(ts, dbuffer, adapt(result));
node expected {resp3::type::simple_string, 1UL, 0UL, {""}};
check_equal(result, expected, "simple_string (empty-node-sync)");
}
{
std::string result;
resp3::read(ts, dbuffer, adapt(result));
std::string expected {""};
check_equal(result, expected, "simple_string (empty-string-sync)");
}
{
std::optional<std::string> result;
resp3::read(ts, dbuffer, adapt(result));
std::optional<std::string> expected {""};
check_equal(result, expected, "simple_string (empty-optional-string-sync)");
}
}
net::awaitable<void> simple_string_async()
{
test_tcp_socket ts {"+OK\r\n"};
std::string rbuffer;
auto dbuffer = net::dynamic_buffer(rbuffer);
{
node result;
co_await resp3::async_read(ts, dbuffer, adapt(result));
node expected {resp3::type::simple_string, 1UL, 0UL, {"OK"}};
check_equal(result, expected, "simple_string (node-async)");
}
{
std::string result;
co_await resp3::async_read(ts, dbuffer, adapt(result));
std::string expected {"OK"};
check_equal(result, expected, "simple_string (string-async)");
}
{
std::optional<std::string> result;
co_await resp3::async_read(ts, dbuffer, adapt(result));
std::optional<std::string> expected {"OK"};
check_equal(result, expected, "simple_string (optional-string-async)");
}
}
net::awaitable<void> simple_string_async_empty()
{
test_tcp_socket ts {"+\r\n"};
std::string rbuffer;
auto dbuffer = net::dynamic_buffer(rbuffer);
{
node result;
co_await resp3::async_read(ts, dbuffer, adapt(result));
node expected {resp3::type::simple_string, 1UL, 0UL, {""}};
check_equal(result, expected, "simple_string (empty-node-async)");
}
{
std::string result;
co_await resp3::async_read(ts, dbuffer, adapt(result));
std::string expected {""};
check_equal(result, expected, "simple_string (empty-string-async)");
}
{
std::optional<std::string> result;
co_await resp3::async_read(ts, dbuffer, adapt(result));
std::optional<std::string> expected {""};
check_equal(result, expected, "simple_string (empty-optional-string-async)");
}
}
// TODO: Test a large simple string. For example
// std::string str(10000, 'a');
// std::string cmd;
// cmd += '+';
// cmd += str;
// cmd += "\r\n";
//-------------------------------------------------------------------------
net::awaitable<void> test_simple_error_async()
{
std::string buf;
{
test_tcp_socket ts {"-Error\r\n"};
node result;
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(result));
node expected {resp3::type::simple_error, 1UL, 0UL, {"Error"}};
check_equal(result, expected, "simple_error (node-async)");
}
{
test_tcp_socket ts {"-Error\r\n"};
std::string result;
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(result));
std::string expected {"Error"};
check_equal(result, expected, "simple_error (string-async)");
}
{
test_tcp_socket ts {"-\r\n"};
std::string result;
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(result));
std::string expected {""};
check_equal(result, expected, "simple_error (empty-string-async)");
}
// TODO: Test with optional.
// TODO: Test with a very long string?
}
//-------------------------------------------------------------------------
net::awaitable<void> test_number()
{
std::string buf;
{
test_tcp_socket ts {":-3\r\n"};
node result;
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(result));
node expected {resp3::type::number, 1UL, 0UL, {"-3"}};
check_equal(result, expected, "number (node-async)");
}
{
test_tcp_socket ts {":-3\r\n"};
long long result;
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(result));
check_equal(result, -3LL, "number (signed-async)");
}
{
test_tcp_socket ts {":3\r\n"};
std::size_t result;
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(result));
check_equal(result, 3UL, "number (unsigned-async)");
}
}
//-------------------------------------------------------------------------
net::awaitable<void> array_async()
{
test_tcp_socket ts {"*3\r\n$2\r\n11\r\n$2\r\n22\r\n$1\r\n3\r\n"};
std::string buf;
auto dbuf = net::dynamic_buffer(buf);
{
std::vector<node> result;
boost::system::error_code ec;
co_await resp3::async_read(ts, dbuf, adapt(result), net::redirect_error(net::use_awaitable, ec));
std::vector<node> expected
{ {resp3::type::array, 3UL, 0UL, {}}
, {resp3::type::blob_string, 1UL, 1UL, {"11"}}
, {resp3::type::blob_string, 1UL, 1UL, {"22"}}
, {resp3::type::blob_string, 1UL, 1UL, {"3"}}
};
check_equal(result, expected, "array (node-async)");
}
{
std::vector<int> result;
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(result));
std::vector<int> expected {11, 22, 3};
check_equal(result, expected, "array (int-async)");
}
{
test_tcp_socket ts {"*0\r\n"};
std::vector<int> result;
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(result));
std::vector<int> expected;
check_equal(result, expected, "array (empty)");
}
}
//-------------------------------------------------------------------------
net::awaitable<void> test_blob_string()
{
using namespace aedis;
std::string buf;
{
std::string cmd {"$2\r\nhh\r\n"};
test_tcp_socket ts {cmd};
gresp.clear();
std::vector<node> expected
{ {resp3::type::blob_string, 1UL, 0UL, {"hh"}} };
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(gresp));
check_equal(gresp, expected, "blob_string");
}
{
std::string cmd {"$26\r\nhhaa\aaaa\raaaaa\r\naaaaaaaaaa\r\n"};
test_tcp_socket ts {cmd};
gresp.clear();
std::vector<node> expected
{ {resp3::type::blob_string, 1UL, 0UL, {"hhaa\aaaa\raaaaa\r\naaaaaaaaaa"}} };
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(gresp));
check_equal(gresp, expected, "blob_string (with separator)");
}
{
std::string cmd {"$0\r\n\r\n"};
test_tcp_socket ts {cmd};
gresp.clear();
std::vector<node> expected
{ {resp3::type::blob_string, 1UL, 0UL, {}} };
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(gresp));
check_equal(gresp, expected, "blob_string (size 0)");
}
}
net::awaitable<void> test_floating_point()
{
using namespace aedis;
std::string buf;
{
std::string cmd {",1.23\r\n"};
test_tcp_socket ts {cmd};
std::vector<node> resp;
std::vector<node> expected
{ {resp3::type::doublean, 1UL, 0UL, {"1.23"}} };
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(resp));
check_equal(resp, expected, "double");
}
{
std::string cmd {",inf\r\n"};
test_tcp_socket ts {cmd};
std::vector<node> resp;
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(resp));
std::vector<node> expected
{ {resp3::type::doublean, 1UL, 0UL, {"inf"}} };
check_equal(resp, expected, "double (inf)");
}
{
std::string cmd {",-inf\r\n"};
test_tcp_socket ts {cmd};
std::vector<node> resp;
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(resp));
std::vector<node> expected
{ {resp3::type::doublean, 1UL, 0UL, {"-inf"}} };
check_equal(resp, expected, "double (-inf)");
}
}
net::awaitable<void> test_boolean()
{
using namespace aedis;
std::string buf;
{
std::string cmd {"#f\r\n"};
test_tcp_socket ts {cmd};
std::vector<node> resp;
std::vector<node> expected
{ {resp3::type::boolean, 1UL, 0UL, {"f"}} };
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(resp));
check_equal(resp, expected, "bool (false)");
}
{
std::string cmd {"#t\r\n"};
test_tcp_socket ts {cmd};
std::vector<node> resp;
std::vector<node> expected
{ {resp3::type::boolean, 1UL, 0UL, {"t"}} };
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(resp));
check_equal(resp, expected, "bool (true)");
}
}
net::awaitable<void> test_blob_error()
{
using namespace aedis;
std::string buf;
{
std::string cmd {"!21\r\nSYNTAX invalid syntax\r\n"};
test_tcp_socket ts {cmd};
gresp.clear();
std::vector<node> expected
{ {resp3::type::blob_error, 1UL, 0UL, {"SYNTAX invalid syntax"}} };
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(gresp));
check_equal(gresp, expected, "blob_error (message)");
}
{
std::string cmd {"!0\r\n\r\n"};
test_tcp_socket ts {cmd};
gresp.clear();
std::vector<node> expected
{ {resp3::type::blob_error, 1UL, 0UL, {}} };
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(gresp));
check_equal(gresp, expected, "blob_error (empty message)");
}
}
net::awaitable<void> test_verbatim_string()
{
using namespace aedis;
std::string buf;
{
std::string cmd {"=15\r\ntxt:Some string\r\n"};
test_tcp_socket ts {cmd};
gresp.clear();
std::vector<node> expected
{ {resp3::type::verbatim_string, 1UL, 0UL, {"txt:Some string"}} };
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(gresp));
check_equal(gresp, expected, "verbatim_string");
}
{
std::string cmd {"=0\r\n\r\n"};
test_tcp_socket ts {cmd};
gresp.clear();
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(gresp));
std::vector<node> expected
{ {resp3::type::verbatim_string, 1UL, 0UL, {}} };
check_equal(gresp, expected, "verbatim_string (empty)");
}
}
net::awaitable<void> test_set2()
{
using namespace aedis;
std::string buf;
{
std::string cmd {"~5\r\n+orange\r\n+apple\r\n+one\r\n+two\r\n+three\r\n"};
test_tcp_socket ts {cmd};
gresp.clear();
std::vector<node> expected
{ {resp3::type::set, 5UL, 0UL, {}}
, {resp3::type::simple_string, 1UL, 1UL, {"orange"}}
, {resp3::type::simple_string, 1UL, 1UL, {"apple"}}
, {resp3::type::simple_string, 1UL, 1UL, {"one"}}
, {resp3::type::simple_string, 1UL, 1UL, {"two"}}
, {resp3::type::simple_string, 1UL, 1UL, {"three"}}
};
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(gresp));
check_equal(gresp, expected, "test set (1)");
}
{
std::string cmd {"~0\r\n"};
test_tcp_socket ts {cmd};
gresp.clear();
std::vector<node> expected
{ {resp3::type::set, 0UL, 0UL, {}}
};
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(gresp));
check_equal(gresp, expected, "test set (2)");
}
}
net::awaitable<void> test_flat_map_async()
{
std::string buf;
auto dbuf = net::dynamic_buffer(buf);
{
test_tcp_socket ts {"%3\r\n$6\r\nserver\r\n$5\r\nredis\r\n$7\r\nversion\r\n$5\r\n6.0.9\r\n$5\r\nproto\r\n:3\r\n"};
std::map<std::string, std::string> result;
co_await resp3::async_read(ts, dbuf, adapt(result));
std::map<std::string, std::string> expected
{ {"server", "redis"}
, {"version", "6.0.9"}
, {"proto", "3"}
};
check_equal(result, expected, "map (flat-map-async)");
}
{
test_tcp_socket ts {"%0\r\n"};
std::map<std::string, std::string> result;
co_await resp3::async_read(ts, dbuf, adapt(result));
std::map<std::string, std::string> expected;
check_equal(result, expected, "map (flat-empty-map-async)");
}
{
// TODO: Why do we get a crash when %3. It should produce an
// error instead.
test_tcp_socket ts {"%2\r\n$6\r\nserver\r\n$2\r\n10\r\n$7\r\nversion\r\n$2\r\n30\r\n"};
std::map<std::string, int> result;
co_await resp3::async_read(ts, dbuf, adapt(result));
std::map<std::string, int> expected
{ {"server", 10}
, {"version", 30}
};
check_equal(result, expected, "map (flat-map-string-int-async)");
}
// TODO: Test optional map.
// TODO: Test serializaition with different key and value types.
}
net::awaitable<void> test_map()
{
using namespace aedis;
std::string buf;
{
std::string cmd {"%7\r\n$6\r\nserver\r\n$5\r\nredis\r\n$7\r\nversion\r\n$5\r\n6.0.9\r\n$5\r\nproto\r\n:3\r\n$2\r\nid\r\n:203\r\n$4\r\nmode\r\n$10\r\nstandalone\r\n$4\r\nrole\r\n$6\r\nmaster\r\n$7\r\nmodules\r\n*0\r\n"};
test_tcp_socket ts {cmd};
gresp.clear();
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(gresp));
std::vector<node> expected
{ {resp3::type::map, 7UL, 0UL, {}}
, {resp3::type::blob_string, 1UL, 1UL, {"server"}}
, {resp3::type::blob_string, 1UL, 1UL, {"redis"}}
, {resp3::type::blob_string, 1UL, 1UL, {"version"}}
, {resp3::type::blob_string, 1UL, 1UL, {"6.0.9"}}
, {resp3::type::blob_string, 1UL, 1UL, {"proto"}}
, {resp3::type::number, 1UL, 1UL, {"3"}}
, {resp3::type::blob_string, 1UL, 1UL, {"id"}}
, {resp3::type::number, 1UL, 1UL, {"203"}}
, {resp3::type::blob_string, 1UL, 1UL, {"mode"}}
, {resp3::type::blob_string, 1UL, 1UL, {"standalone"}}
, {resp3::type::blob_string, 1UL, 1UL, {"role"}}
, {resp3::type::blob_string, 1UL, 1UL, {"master"}}
, {resp3::type::blob_string, 1UL, 1UL, {"modules"}}
, {resp3::type::array, 0UL, 1UL, {}}
};
check_equal(gresp, expected, "test map");
}
{
std::string cmd {"%0\r\n"};
test_tcp_socket ts {cmd};
gresp.clear();
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(gresp));
std::vector<node> expected
{ {resp3::type::map, 0UL, 0UL, {}} };
check_equal(gresp, expected, "test map (empty)");
}
}
net::awaitable<void> test_streamed_string()
{
using namespace aedis;
std::string buf;
{
std::string cmd {"$?\r\n;4\r\nHell\r\n;5\r\no wor\r\n;1\r\nd\r\n;0\r\n"};
test_tcp_socket ts {cmd};
gresp.clear();
std::vector<node> expected
{ {resp3::type::streamed_string_part, 1UL, 0UL, {"Hello world"}} };
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(gresp));
check_equal(gresp, expected, "streamed string");
}
{
std::string cmd {"$?\r\n;0\r\n"};
test_tcp_socket ts {cmd};
std::vector<node> resp;
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(resp));
std::vector<node> expected
{ {resp3::type::streamed_string_part, 1UL, 0UL, {}} };
check_equal(resp, expected, "streamed string (empty)");
}
}
//net::awaitable<void> offline()
//{
// std::string buf;
// //{
// // std::string cmd {"|1\r\n+key-popularity\r\n%2\r\n$1\r\na\r\n,0.1923\r\n$1\r\nb\r\n,0.0012\r\n"};
// // test_tcp_socket ts {cmd};
// // resp3::flat_radapter res;
// // co_await async_read(ts, buf, res);
// // check_equal(res.result, {"key-popularity", "a", "0.1923", "b", "0.0012"}, "attribute");
// //}
//
// //{
// // std::string cmd {">4\r\n+pubsub\r\n+message\r\n+foo\r\n+bar\r\n"};
// // test_tcp_socket ts {cmd};
// // resp3::flat_radapter res;
// // co_await async_read(ts, buf, res);
// // check_equal(res.result, {"pubsub", "message", "foo", "bar"}, "push type");
// //}
//
// //{
// // std::string cmd {">0\r\n"};
// // test_tcp_socket ts {cmd};
// // resp3::flat_radapter res;
// // co_await async_read(ts, buf, res);
// // check_equal(res.result, {}, "push type (empty)");
// //}
//}
net::awaitable<void> optional_async()
{
test_tcp_socket ts {"_\r\n"};
std::string buf;
auto dbuf = net::dynamic_buffer(buf);
{
node result;
co_await resp3::async_read(ts, dbuf, adapt(result));
node expected {resp3::type::null, 1UL, 0UL, {""}};
check_equal(result, expected, "optional (node-async)");
}
{
int result;
boost::system::error_code ec;
co_await resp3::async_read(ts, dbuf, adapt(result), net::redirect_error(net::use_awaitable, ec));
//auto const expected = make_error_code(adapter::error::null);
//std::cout << expected.message() << std::endl;
// TODO: Convert to std::error_code.
//check_equal(ec, expected, "optional (int-async)");
}
{
std::optional<int> result;
co_await resp3::async_read(ts, dbuf, adapt(result));
std::optional<int> expected;
check_equal(result, expected, "optional (optional-int-async)");
}
{
std::optional<std::string> result;
co_await resp3::async_read(ts, dbuf, adapt(result));
std::optional<std::string> expected;
check_equal(result, expected, "optional (optional-int-async)");
}
}
int main()
{
simple_string_sync();
simple_string_sync_empty();
net::io_context ioc {1};
co_spawn(ioc, simple_string_async(), net::detached);
co_spawn(ioc, simple_string_async_empty(), net::detached);
co_spawn(ioc, test_simple_error_async(), net::detached);
co_spawn(ioc, test_number(), net::detached);
co_spawn(ioc, test_map(), net::detached);
co_spawn(ioc, test_flat_map_async(), net::detached);
co_spawn(ioc, optional_async(), net::detached);
co_spawn(ioc, array_async(), net::detached);
co_spawn(ioc, test_blob_string(), net::detached);
co_spawn(ioc, test_floating_point(), net::detached);
co_spawn(ioc, test_boolean(), net::detached);
co_spawn(ioc, test_blob_error(), net::detached);
co_spawn(ioc, test_verbatim_string(), net::detached);
co_spawn(ioc, test_set2(), net::detached);
ioc.run();
}

View File

@@ -1,581 +0,0 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include <iostream>
#include <map>
#include <aedis/aedis.hpp>
#include <aedis/src.hpp>
#include "test_stream.hpp"
#include "check.hpp"
namespace net = aedis::net;
using tcp = net::ip::tcp;
using tcp_socket = net::use_awaitable_t<>::as_default_on_t<tcp::socket>;
using test_tcp_socket = net::use_awaitable_t<>::as_default_on_t<aedis::test_stream<aedis::net::system_executor>>;
namespace this_coro = net::this_coro;
using namespace aedis;
using namespace aedis::resp3;
using aedis::redis::command;
using aedis::redis::make_serializer;
std::vector<node> gresp;
//-------------------------------------------------------------------
net::awaitable<void>
test_general(net::ip::tcp::resolver::results_type const& res)
{
auto ex = co_await this_coro::executor;
std::vector<int> list_ {1 ,2, 3, 4, 5, 6};
std::string set_ {"aaa"};
//----------------------------------
std::string request;
auto sr = make_serializer(request);
sr.push(command::hello, 3);
sr.push(command::flushall);
sr.push_range(command::rpush, "a", std::cbegin(list_), std::cend(list_));
sr.push(command::llen, "a");
sr.push(command::lrange, "a", 0, -1);
sr.push(command::ltrim, "a", 2, -2);
sr.push(command::lpop, "a");
//sr.lpop("a", 2); // Not working?
sr.push(command::set, "b", set_);
sr.push(command::get, "b");
sr.push(command::append, "b", "b");
sr.push(command::del, "b");
sr.push(command::subscribe, "channel");
sr.push(command::incr, "3");
// transaction
for (auto i = 0; i < 3; ++i) {
sr.push(command::multi);
sr.push(command::ping);
sr.push(command::lrange, "a", 0, -1);
sr.push(command::ping);
// TODO: It looks like we can't publish to a channel we
// are already subscribed to from inside a transaction.
//req.publish("some-channel", "message1");
sr.push(command::exec);
}
std::map<std::string, std::string> m1 =
{ {"field1", "value1"}
, {"field2", "value2"}};
sr.push_range(command::hset, "d", std::cbegin(m1), std::cend(m1));
sr.push(command::hget, "d", "field2");
sr.push(command::hgetall, "d");
sr.push(command::hdel, "d", "field1", "field2"); // TODO: Test as range too.
sr.push(command::hincrby, "e", "some-field", 10);
sr.push(command::zadd, "f", 1, "Marcelo");
sr.push(command::zrange, "f", 0, 1);
sr.push(command::zrangebyscore, "f", 1, 1);
sr.push(command::zremrangebyscore, "f", "-inf", "+inf");
auto const v = std::vector<int>{1, 2, 3};
sr.push_range(command::sadd, "g", std::cbegin(v), std::cend(v));
sr.push(command::smembers, "g");
sr.push(command::quit);
//----------------------------------
net::ip::tcp::socket socket{ex};
co_await net::async_connect(socket, res, net::use_awaitable);
co_await net::async_write(socket, net::buffer(request), net::use_awaitable);
// Reads the responses.
std::string buffer;
// hello
co_await resp3::async_read(socket, net::dynamic_buffer(buffer), adapt(), net::use_awaitable);
// flushall
co_await resp3::async_read(socket, net::dynamic_buffer(buffer), adapt(), net::use_awaitable);
{ // rpush:
std::vector<node> resp;
co_await resp3::async_read(socket, net::dynamic_buffer(buffer), adapt(resp), net::use_awaitable);
auto const n = std::to_string(std::size(list_));
std::vector<node> expected
{ {resp3::type::number, 1UL, 0UL, n} };
check_equal(resp, expected, "rpush (value)");
}
{ // llen
std::vector<node> resp;
co_await resp3::async_read(socket, net::dynamic_buffer(buffer), adapt(resp), net::use_awaitable);
std::vector<node> expected
{ {resp3::type::number, 1UL, 0UL, {"6"}} };
check_equal(resp, expected, "llen");
}
{ // lrange
std::vector<node> resp;
co_await resp3::async_read(socket, net::dynamic_buffer(buffer), adapt(resp), net::use_awaitable);
std::vector<node> expected
{ {resp3::type::array, 6UL, 0UL, {}}
, {resp3::type::blob_string, 1UL, 1UL, {"1"}}
, {resp3::type::blob_string, 1UL, 1UL, {"2"}}
, {resp3::type::blob_string, 1UL, 1UL, {"3"}}
, {resp3::type::blob_string, 1UL, 1UL, {"4"}}
, {resp3::type::blob_string, 1UL, 1UL, {"5"}}
, {resp3::type::blob_string, 1UL, 1UL, {"6"}}
};
check_equal(resp, expected, "lrange ");
}
{ // ltrim
std::vector<node> resp;
co_await resp3::async_read(socket, net::dynamic_buffer(buffer), adapt(resp), net::use_awaitable);
std::vector<node> expected
{ {resp3::type::simple_string, 1UL, 0UL, {"OK"}} };
check_equal(resp, expected, "ltrim");
}
{ // lpop
std::vector<node> resp;
co_await resp3::async_read(socket, net::dynamic_buffer(buffer), adapt(resp), net::use_awaitable);
std::vector<node> expected
{ {resp3::type::blob_string, 1UL, 0UL, {"3"}} };
check_equal(resp, expected, "lpop");
}
//{ // lpop
// std::vector<node> resp;
// co_await resp3::async_read(socket, net::dynamic_buffer(buffer), adapt(resp), net::use_awaitable);
// std::vector<node> expected
// { {resp3::type::array, 2UL, 0UL, {}}
// , {resp3::type::array, 1UL, 1UL, {"4"}}
// , {resp3::type::array, 1UL, 1UL, {"5"}}
// };
// check_equal(resp, expected, "lpop");
//}
//{ // lrange
// static int c = 0;
// if (c == 0) {
// std::vector<node> expected
// { {resp3::type::array, 6UL, 0UL, {}}
// , {resp3::type::blob_string, 1UL, 1UL, {"1"}}
// , {resp3::type::blob_string, 1UL, 1UL, {"2"}}
// , {resp3::type::blob_string, 1UL, 1UL, {"3"}}
// , {resp3::type::blob_string, 1UL, 1UL, {"4"}}
// , {resp3::type::blob_string, 1UL, 1UL, {"5"}}
// , {resp3::type::blob_string, 1UL, 1UL, {"6"}}
// };
// check_equal(resp, expected, "lrange ");
// } else {
// std::vector<node> expected
// { {resp3::type::simple_string, 1UL, 0UL, {"QUEUED"}} };
// check_equal(resp, expected, "lrange (inside transaction)");
// }
//
// ++c;
//}
//for (;;) {
// switch (cmd) {
// case command::multi:
// {
// std::vector<node> expected
// { {resp3::type::simple_string, 1UL, 0UL, {"OK"}} };
// check_equal(resp, expected, "multi");
// } break;
// case command::ping:
// {
// std::vector<node> expected
// { {resp3::type::simple_string, 1UL, 0UL, {"QUEUED"}} };
// check_equal(resp, expected, "ping");
// } break;
// case command::set:
// {
// std::vector<node> expected
// { {resp3::type::simple_string, 1UL, 0UL, {"OK"}} };
// check_equal(resp, expected, "set");
// } break;
// case command::quit:
// {
// std::vector<node> expected
// { {resp3::type::simple_string, 1UL, 0UL, {"OK"}} };
// check_equal(resp, expected, "quit");
// } break;
// case command::flushall:
// {
// std::vector<node> expected
// { {resp3::type::simple_string, 1UL, 0UL, {"OK"}} };
// check_equal(resp, expected, "flushall");
// } break;
// case command::append:
// {
// std::vector<node> expected
// { {resp3::type::number, 1UL, 0UL, {"4"}} };
// check_equal(resp, expected, "append");
// } break;
// case command::hset:
// {
// std::vector<node> expected
// { {resp3::type::number, 1UL, 0UL, {"2"}} };
// check_equal(resp, expected, "hset");
// } break;
// case command::del:
// {
// std::vector<node> expected
// { {resp3::type::number, 1UL, 0UL, {"1"}} };
// check_equal(resp, expected, "del");
// } break;
// case command::incr:
// {
// std::vector<node> expected
// { {resp3::type::number, 1UL, 0UL, {"1"}} };
// check_equal(resp, expected, "incr");
// } break;
// case command::publish:
// {
// std::vector<node> expected
// { {resp3::type::number, 1UL, 0UL, {"1"}} };
// check_equal(resp, expected, "publish");
// } break;
// case command::hincrby:
// {
// std::vector<node> expected
// { {resp3::type::number, 1UL, 0UL, {"10"}} };
// check_equal(resp, expected, "hincrby");
// } break;
// case command::zadd:
// {
// std::vector<node> expected
// { {resp3::type::number, 1UL, 0UL, {"1"}} };
// check_equal(resp, expected, "zadd");
// } break;
// case command::sadd:
// {
// std::vector<node> expected
// { {resp3::type::number, 1UL, 0UL, {"3"}} };
// check_equal(resp, expected, "sadd");
// } break;
// case command::hdel:
// {
// std::vector<node> expected
// { {resp3::type::number, 1UL, 0UL, {"2"}} };
// check_equal(resp, expected, "hdel");
// } break;
// case command::zremrangebyscore:
// {
// std::vector<node> expected
// { {resp3::type::number, 1UL, 0UL, {"1"}} };
// check_equal(resp, expected, "zremrangebyscore");
// } break;
// case command::get:
// {
// std::vector<node> expected
// { {resp3::type::blob_string, 1UL, 0UL, test.set_} };
// check_equal(resp, expected, "get");
// } break;
// case command::hget:
// {
// std::vector<node> expected
// { {resp3::type::blob_string, 1UL, 0UL, std::string{"value2"}} };
// check_equal(resp, expected, "hget");
// } break;
// case command::hvals:
// {
// std::vector<node> expected
// { {resp3::type::array, 2UL, 0UL, {}}
// , {resp3::type::array, 1UL, 1UL, {"value1"}}
// , {resp3::type::array, 1UL, 1UL, {"value2"}}
// };
// check_equal(resp, expected, "hvals");
// } break;
// case command::zrange:
// {
// std::vector<node> expected
// { {resp3::type::array, 1UL, 0UL, {}}
// , {resp3::type::blob_string, 1UL, 1UL, {"Marcelo"}}
// };
// check_equal(resp, expected, "hvals");
// } break;
// case command::zrangebyscore:
// {
// std::vector<node> expected
// { {resp3::type::array, 1UL, 0UL, {}}
// , {resp3::type::blob_string, 1UL, 1UL, {"Marcelo"}}
// };
// check_equal(resp, expected, "zrangebyscore");
// } break;
// case command::exec:
// {
// std::vector<node> expected
// { {resp3::type::array, 3UL, 0UL, {}}
// , {resp3::type::simple_string, 1UL, 1UL, {"PONG"}}
// , {resp3::type::array, 2UL, 1UL, {}}
// , {resp3::type::blob_string, 1UL, 2UL, {"4"}}
// , {resp3::type::blob_string, 1UL, 2UL, {"5"}}
// , {resp3::type::simple_string, 1UL, 1UL, {"PONG"}}
// };
// check_equal(resp, expected, "transaction");
// } break;
// case command::hgetall:
// {
// std::vector<node> expected
// { {resp3::type::map, 2UL, 0UL, {}}
// , {resp3::type::blob_string, 1UL, 1UL, {"field1"}}
// , {resp3::type::blob_string, 1UL, 1UL, {"value1"}}
// , {resp3::type::blob_string, 1UL, 1UL, {"field2"}}
// , {resp3::type::blob_string, 1UL, 1UL, {"value2"}}
// };
// check_equal(resp, expected, "hgetall (value)");
// } break;
// case command::smembers:
// {
// std::vector<node> expected
// { {resp3::type::set, 3UL, 0UL, {}}
// , {resp3::type::blob_string, 1UL, 1UL, {"1"}}
// , {resp3::type::blob_string, 1UL, 1UL, {"2"}}
// , {resp3::type::blob_string, 1UL, 1UL, {"3"}}
// };
// check_equal(resp, expected, "smembers (value)");
// } break;
// default: { std::cout << "Error: " << resp.front().data_type << " " << cmd << std::endl; }
// }
// resp.clear();
//}
}
//-------------------------------------------------------------------
//net::awaitable<void>
//test_list(net::ip::tcp::resolver::results_type const& results)
//{
// std::vector<int> list {1 ,2, 3, 4, 5, 6};
//
// resp3::serializer p;
// p.push(command::hello, 3);
// p.push(command::flushall);
// p.push_range(command::rpush, "a", std::cbegin(list), std::cend(list));
// p.push(command::lrange, "a", 0, -1);
// p.push(command::lrange, "a", 2, -2);
// p.push(command::ltrim, "a", 2, -2);
// p.push(command::lpop, "a");
// p.push(command::quit);
//
// auto ex = co_await this_coro::executor;
// tcp_socket socket {ex};
// co_await async_connect(socket, results);
// co_await async_write(socket, net::buffer(p.payload));
// std::string buf;
//
// { // hello
// gresp.clear();
// co_await async_read(socket, buf, gresp);
// }
//
// { // flushall
// gresp.clear();
// co_await async_read(socket, buf, gresp);
// std::vector<node> expected
// { {resp3::type::simple_string, 1UL, 0UL, {"OK"}} };
// check_equal(gresp, expected, "flushall");
// }
//
// { // rpush
// gresp.clear();
// std::vector<node> expected
// { {resp3::type::number, 1UL, 0UL, {"6"}} };
// co_await async_read(socket, buf, gresp);
// check_equal(gresp, expected, "rpush");
// }
//
// { // lrange
// resp3::flat_array_int_type buffer;
// resp3::detail::basic_flat_array_adapter<int> res{&buffer};
// co_await async_read(socket, buf, res);
// check_equal(buffer, list, "lrange-1");
// }
//
// { // lrange
// resp3::flat_array_int_type buffer;
// resp3::detail::basic_flat_array_adapter<int> res{&buffer};
// co_await async_read(socket, buf, res);
// check_equal(buffer, std::vector<int>{3, 4, 5}, "lrange-2");
// }
//
// { // ltrim
// gresp.clear();
// std::vector<node> expected
// { {resp3::type::simple_string, 1UL, 0UL, {"OK"}} };
//
// co_await async_read(socket, buf, gresp);
// check_equal(gresp, expected, "ltrim");
// }
//
// { // lpop. Why a blob string instead of a number?
// gresp.clear();
// std::vector<node> expected
// { {resp3::type::blob_string, 1UL, 0UL, {"3"}} };
//
// co_await async_read(socket, buf, gresp);
// check_equal(gresp, expected, "lpop");
// }
//
// { // quit
// gresp.clear();
// co_await async_read(socket, buf, gresp);
// std::vector<node> expected
// { {resp3::type::simple_string, 1UL, 0UL, {"OK"}} };
// check_equal(gresp, expected, "ltrim");
// }
//}
std::string test_bulk1(10000, 'a');
net::awaitable<void>
test_set(net::ip::tcp::resolver::results_type const& results)
{
using namespace aedis;
// Tests whether the parser can handle payloads that contain the separator.
test_bulk1[30] = '\r';
test_bulk1[31] = '\n';
std::string test_bulk2 = "aaaaa";
std::string request;
auto sr = make_serializer(request);
sr.push(command::hello, 3);
sr.push(command::flushall);
sr.push(command::set, "s", test_bulk1);
sr.push(command::get, "s");
sr.push(command::set, "s", test_bulk2);
sr.push(command::get, "s");
sr.push(command::set, "s", "");
sr.push(command::get, "s");
sr.push(command::quit);
auto ex = co_await this_coro::executor;
tcp_socket socket {ex};
co_await async_connect(socket, results);
co_await net::async_write(socket, net::buffer(request));
std::string buf;
{ // hello, flushall
gresp.clear();
co_await resp3::async_read(socket, net::dynamic_buffer(buf), adapt(gresp));
co_await resp3::async_read(socket, net::dynamic_buffer(buf), adapt(gresp));
}
{ // set
gresp.clear();
co_await resp3::async_read(socket, net::dynamic_buffer(buf), adapt(gresp));
std::vector<node> expected
{ {resp3::type::simple_string, 1UL, 0UL, {"OK"}} };
check_equal(gresp, expected, "set1");
}
{ // get
gresp.clear();
std::vector<node> expected
{ {resp3::type::blob_string, 1UL, 0UL, test_bulk1} };
co_await resp3::async_read(socket, net::dynamic_buffer(buf), adapt(gresp));
check_equal(gresp, expected, "get1");
}
{ // set
gresp.clear();
co_await resp3::async_read(socket, net::dynamic_buffer(buf), adapt(gresp));
std::vector<node> expected
{ {resp3::type::simple_string, 1UL, 0UL, {"OK"}} };
check_equal(gresp, expected, "ltrim");
}
{ // get
gresp.clear();
std::vector<node> expected
{ {resp3::type::blob_string, 1UL, 0UL, test_bulk2} };
co_await resp3::async_read(socket, net::dynamic_buffer(buf), adapt(gresp));
check_equal(gresp, expected, "get2");
}
{ // set
gresp.clear();
co_await resp3::async_read(socket, net::dynamic_buffer(buf), adapt(gresp));
std::vector<node> expected
{ {resp3::type::simple_string, 1UL, 0UL, {"OK"}} };
check_equal(gresp, expected, "set3");
}
{ // get
gresp.clear();
std::vector<node> expected
{ {resp3::type::blob_string, 1UL, 0UL, {}} };
co_await resp3::async_read(socket, net::dynamic_buffer(buf), adapt(gresp));
check_equal(gresp, expected, "get3");
}
{ // quit
gresp.clear();
co_await resp3::async_read(socket, net::dynamic_buffer(buf), adapt(gresp));
std::vector<node> expected
{ {resp3::type::simple_string, 1UL, 0UL, {"OK"}} };
check_equal(gresp, expected, "quit");
}
}
int main()
{
net::io_context ioc {1};
tcp::resolver resv(ioc);
auto const res = resv.resolve("127.0.0.1", "6379");
//co_spawn(ioc, test_list(res), net::detached);
co_spawn(ioc, test_set(res), net::detached);
co_spawn(ioc, test_general(res), net::detached);
ioc.run();
}

View File

@@ -1,110 +0,0 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres at gmail dot com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include <aedis/aedis.hpp>
namespace aedis {
struct initiate_async_receive {
using executor_type = aedis::net::system_executor;
std::string::const_iterator pbegin;
std::string::const_iterator pend;
executor_type get_executor() noexcept
{ return aedis::net::system_executor(); }
template <
class ReadHandler,
class MutableBufferSequence>
void operator()(
ReadHandler&& handler,
MutableBufferSequence const& buffers)
{
boost::system::error_code ec;
if (std::size(buffers) == 0) {
handler(ec, 0);
return;
}
auto begin = boost::asio::buffer_sequence_begin(buffers);
auto end = boost::asio::buffer_sequence_end(buffers);
//std::cout << "Buffers size: " << std::size(buffers) << std::endl;
std::size_t transferred = 0;
while (begin != end) {
//std::cout << "Buffer size: " << std::ssize(*begin) << std::endl;
auto const min = std::min(std::ssize(*begin), pend - pbegin);
std::copy(pbegin, pbegin + min, static_cast<char*>(begin->data()));
std::advance(pbegin, min);
transferred += min;
++begin;
}
handler(ec, transferred);
}
};
template <class Executor>
struct test_stream {
std::string const payload;
using executor_type = Executor;
template<
class MutableBufferSequence,
class ReadHandler =
aedis::net::default_completion_token_t<executor_type>
>
auto async_read_some(
MutableBufferSequence const& buffers,
ReadHandler&& handler = net::default_completion_token_t<executor_type>{})
{
return aedis::net::async_initiate<ReadHandler,
void (boost::system::error_code, std::size_t)>(
initiate_async_receive
{std::cbegin(payload), std::cend(payload)},
handler, buffers);
}
template<class MutableBufferSequence>
std::size_t read_some(
MutableBufferSequence const& buffers,
boost::system::error_code&)
{
if (std::size(buffers) == 0)
return 0;
boost::system::error_code ec;
auto pbegin = std::cbegin(payload);
auto pend = std::cend(payload);
auto begin = boost::asio::buffer_sequence_begin(buffers);
auto end = boost::asio::buffer_sequence_end(buffers);
std::size_t transferred = 0;
while (begin != end) {
auto const min = std::min(std::ssize(*begin), pend - pbegin);
std::copy(pbegin, pbegin + min, static_cast<char*>(begin->data()));
std::advance(pbegin, min);
transferred += min;
++begin;
}
return transferred;
}
executor_type get_executor() noexcept
{ return aedis::net::system_executor(); }
template<class Executor1>
struct rebind_executor {
using other = test_stream<Executor1>;
};
};
}

View File

@@ -1,24 +1,25 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres.gmail.com)
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#include <iostream>
#include <algorithm>
#include <cctype>
#include <boost/asio/connect.hpp>
#include <aedis/aedis.hpp>
#include <aedis/src.hpp>
namespace net = boost::asio;
namespace resp3 = aedis::resp3;
using aedis::redis::command;
using aedis::redis::make_serializer;
using resp3::adapt;
using resp3::node;
namespace net = aedis::net;
using aedis::redis::command;
using aedis::generic::make_serializer;
using aedis::resp3::node;
using aedis::adapter::adapt;
using net::ip::tcp;
using net::write;
using net::buffer;
@@ -32,17 +33,17 @@ std::string toupper(std::string s)
}
std::vector<std::string>
get_cmd_names(std::vector<node> const& resp)
get_cmd_names(std::vector<node<std::string>> const& resp)
{
if (std::empty(resp)) {
if (resp.empty()) {
std::cerr << "Response is empty." << std::endl;
return {};
}
std::vector<std::string> ret;
for (auto i = 0ULL; i < std::size(resp); ++i) {
for (auto i = 0ULL; i < resp.size(); ++i) {
if (resp.at(i).depth == 1)
ret.push_back(resp.at(i + 1).data);
ret.push_back(resp.at(i + 1).value);
}
std::sort(std::begin(ret), std::end(ret));
@@ -58,7 +59,7 @@ void print_cmds_enum(std::vector<std::string> const& cmds)
<< " " << cmd << ",\n";
}
std::cout << " unknown\n};\n";
std::cout << " invalid\n};\n";
}
void print_cmds_strs(std::vector<std::string> const& cmds)
@@ -78,7 +79,7 @@ int main()
tcp::resolver resv{ioc};
auto const res = resv.resolve("127.0.0.1", "6379");
tcp::socket socket{ioc};
connect(socket, res);
net::connect(socket, res);
std::string request;
auto sr = make_serializer(request);
@@ -87,7 +88,7 @@ int main()
sr.push(command::quit);
write(socket, buffer(request));
std::vector<node> resp;
std::vector<node<std::string>> resp;
std::string buffer;
resp3::read(socket, dynamic_buffer(buffer));