diff --git a/ReleaseNotes.txt b/ReleaseNotes.txt
index 65d9ea794..929b4a27c 100644
--- a/ReleaseNotes.txt
+++ b/ReleaseNotes.txt
@@ -1,3 +1,17 @@
+QDS 3.292:
+
+* [QD-1251] New order source for cboe C2 options
+* [QD-1241] dxFeed API: Extend Order event for Full Order Book
+ - JVM parameter "-Ddxscheme.fob=true" will add Full Order Book fields to Order record
+ - List of Order sources that support Full Order Book can be customized by
+ "-Dcom.dxfeed.event.market.impl.Order.fob.suffixes" parameter (default value="|#NTV")
+* [QD-1244] QDS: Add support for long and timestamp fields in QD schema
+ - Added support for long values and time in millis to com.dxfeed.annotation.EventFieldType
+ (to enable long use @EventFieldMapping(type = EventFieldType.LONG) annotation)
+ - Changed ordering of constants in com.dxfeed.annotation.EventFieldType
+* [QD-1235] QD Core: Disable conflation for Order events
+ - JVM parameter "-Dcom.devexperts.qd.impl.matrix.History.conflateFilter" manages which records are conflated;
+ if "-Ddxscheme.fob=true" is specified, then default value is "!:Order*", else it is "*"
QDS 3.291:
diff --git a/auth/pom.xml b/auth/pom.xml
index d5835fe45..4161c5638 100644
--- a/auth/pom.xml
+++ b/auth/pom.xml
@@ -14,7 +14,7 @@
QD
com.devexperts.qd
- 3.291
+ 3.292
../pom.xml
4.0.0
diff --git a/dxfeed-api/pom.xml b/dxfeed-api/pom.xml
index 0be83d768..053e1ea4b 100644
--- a/dxfeed-api/pom.xml
+++ b/dxfeed-api/pom.xml
@@ -14,7 +14,7 @@
QD
com.devexperts.qd
- 3.291
+ 3.292
../pom.xml
4.0.0
diff --git a/dxfeed-api/src/main/java/com/dxfeed/annotation/EventFieldType.java b/dxfeed-api/src/main/java/com/dxfeed/annotation/EventFieldType.java
index b6d48ff8b..a725db62b 100644
--- a/dxfeed-api/src/main/java/com/dxfeed/annotation/EventFieldType.java
+++ b/dxfeed-api/src/main/java/com/dxfeed/annotation/EventFieldType.java
@@ -25,11 +25,21 @@ public enum EventFieldType {
*/
TRANSIENT,
+ /** @deprecated Use {@link #TIME_SECONDS} instead. */
+ @Deprecated
+ TIME,
+
/**
* This type can be used for {@code long} properties that store time in milliseconds since Java Epoch.
* It will be mapped to a QD field that keeps seconds since Java Epoch (milliseconds will be lost).
*/
- TIME,
+ TIME_SECONDS,
+
+ /**
+ * This type can be used for {@code long} properties that store time in milliseconds since Java Epoch.
+ * It will be mapped to a time millis QD field.
+ */
+ TIME_MILLIS,
/**
* This type can be used for {@code int} properties that store number of days since Java Epoch.
@@ -42,6 +52,13 @@ public enum EventFieldType {
*/
INT,
+ /**
+ * This type can be used for {@code long} properties.
+ * By default primitive longs are mapped to {@link #DECIMAL} - use this type with {@link EventFieldMapping}
+ * annotation to map to a long QD field.
+ */
+ LONG,
+
/**
* This type can be used for all primitive properties.
* It will be mapped to a decimal QD field.
@@ -70,5 +87,5 @@ public enum EventFieldType {
* This type can be used for all reference properties.
* It will be mapped to a serial object QD field.
*/
- MARSHALLED
+ MARSHALLED,
}
diff --git a/dxfeed-api/src/main/java/com/dxfeed/event/market/AnalyticOrder.java b/dxfeed-api/src/main/java/com/dxfeed/event/market/AnalyticOrder.java
index 08e770634..0d4ecb6e7 100644
--- a/dxfeed-api/src/main/java/com/dxfeed/event/market/AnalyticOrder.java
+++ b/dxfeed-api/src/main/java/com/dxfeed/event/market/AnalyticOrder.java
@@ -103,6 +103,19 @@
*
* This event type cannot be used with {@link DXFeed#getLastEvent DXFeed.getLastEvent} method.
*
+ *
+ *
+ * Some feeds provide support for "Full Order Book" (FOB) where additional fields will be available:
+ *
+ * {@link #getAction() action} - event business meaning (see {@link OrderAction} for more details)
+ * {@link #getActionTime() actionTime} - time of the last action
+ * {@link #getOrderId() orderId} - ID of this order
+ * {@link #getAuxOrderId() auxOrderId} - additional ID for this order
+ * {@link #getTradeId() tradeId} - trade (order execution) ID
+ * {@link #getTradePrice() tradePrice} - price of the trade
+ * {@link #getTradeSize() tradeSize} - size of the trade
+ *
+ *
* Implementation details
*
* This event is implemented on top of QDS records {@code AnalyticOrder#},
diff --git a/dxfeed-api/src/main/java/com/dxfeed/event/market/Order.java b/dxfeed-api/src/main/java/com/dxfeed/event/market/Order.java
index 7956df068..33a5868a6 100644
--- a/dxfeed-api/src/main/java/com/dxfeed/event/market/Order.java
+++ b/dxfeed-api/src/main/java/com/dxfeed/event/market/Order.java
@@ -100,6 +100,19 @@
*
* This event type cannot be used with {@link DXFeed#getLastEvent DXFeed.getLastEvent} method.
*
+ *
+ *
+ * Some feeds provide support for "Full Order Book" (FOB) where additional fields will be available:
+ *
+ * {@link #getAction() action} - event business meaning (see {@link OrderAction} for more details)
+ * {@link #getActionTime() actionTime} - time of the last action
+ * {@link #getOrderId() orderId} - ID of this order
+ * {@link #getAuxOrderId() auxOrderId} - additional ID for this order
+ * {@link #getTradeId() tradeId} - trade (order execution) ID
+ * {@link #getTradePrice() tradePrice} - price of the trade
+ * {@link #getTradeSize() tradeSize} - size of the trade
+ *
+ *
* Implementation details
*
* This event is implemented on top of QDS record {@code Quote} for composite quotes with {@link Scope#COMPOSITE},
diff --git a/dxfeed-api/src/main/java/com/dxfeed/event/market/OrderAction.java b/dxfeed-api/src/main/java/com/dxfeed/event/market/OrderAction.java
new file mode 100644
index 000000000..af15a8045
--- /dev/null
+++ b/dxfeed-api/src/main/java/com/dxfeed/event/market/OrderAction.java
@@ -0,0 +1,161 @@
+/*
+ * !++
+ * QDS - Quick Data Signalling Library
+ * !-
+ * Copyright (C) 2002 - 2020 Devexperts LLC
+ * !-
+ * 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/.
+ * !__
+ */
+package com.dxfeed.event.market;
+
+/**
+ * Action enum for the Full Order Book (FOB) Orders. Action describes business meaning of the {@link Order} event:
+ * whether order was added or replaced, partially or fully executed, etc.
+ */
+public enum OrderAction {
+
+ /**
+ * Default enum value for orders that do not support "Full Order Book" and for backward compatibility -
+ * action must be derived from other {@link Order} fields.
+ *
+ * All Full Order Book related fields for this action will be empty.
+ */
+ UNDEFINED(0),
+
+ /**
+ * New Order is added to Order Book.
+ *
+ *
Full Order Book fields:
+ *
+ * {@link Order#getOrderId() orderId} - always present
+ * {@link Order#getAuxOrderId() auxOrderId} - ID of the order replaced by this new order - if available.
+ * Trade fields will be empty
+ *
+ */
+ NEW(1),
+
+ /**
+ * Order is modified and price-time-priority is not maintained (i.e. order has re-entered Order Book).
+ * Order {@link Order#getEventSymbol() symbol} and {@link Order#getOrderSide() side} will remain the same.
+ *
+ * Full Order Book fields:
+ *
+ * {@link Order#getOrderId() orderId} - always present
+ * Trade fields will be empty
+ *
+ */
+ REPLACE(2),
+
+ /**
+ * Order is modified without changing its price-time-priority (usually due to partial cancel by user).
+ * Order's {@link Order#getSize() size} will contain new updated size.
+ *
+ * Full Order Book fields:
+ *
+ * {@link Order#getOrderId() orderId} - always present
+ * Trade fields will be empty
+ *
+ */
+ MODIFY(3),
+
+ /**
+ * Order is fully canceled and removed from Order Book.
+ * Order's {@link Order#getSize() size} will be equal to 0.
+ *
+ * Full Order Book fields:
+ *
+ * {@link Order#getOrderId() orderId} - always present
+ * {@link Order#getAuxOrderId() auxOrderId} - ID of the new order replacing this order - if available.
+ * Trade fields will be empty
+ *
+ */
+ DELETE(4),
+
+ /**
+ * Size is changed (usually reduced) due to partial order execution.
+ * Order's {@link Order#getSize() size} will be updated to show current outstanding size.
+ *
+ * Full Order Book fields:
+ *
+ * {@link Order#getOrderId() orderId} - always present
+ * {@link Order#getAuxOrderId()} auxOrderId} - aggressor order ID, if available
+ * {@link Order#getTradeId() tradeId} - if available
+ * {@link Order#getTradeSize() tradeSize} and {@link Order#getTradePrice() tradePrice} -
+ * contain size and price of this execution
+ *
+ */
+ PARTIAL(5),
+
+ /**
+ * Order is fully executed and removed from Order Book.
+ * Order's {@link Order#getSize() size} will be equals to 0.
+ *
+ * Full Order Book fields:
+ *
+ * {@link Order#getOrderId() orderId} - always present
+ * {@link Order#getAuxOrderId()} auxOrderId} - aggressor order ID, if available
+ * {@link Order#getTradeId() tradeId} - if available
+ * {@link Order#getTradeSize() tradeSize} and {@link Order#getTradePrice() tradePrice} -
+ * contain size and price of this execution - always present
+ *
+ */
+ EXECUTE(6),
+
+ /**
+ * Non-Book Trade - this Trade not refers to any entry in Order Book.
+ * Order's {@link Order#getSize() size} and {@link Order#getPrice() price} will be equals to 0.
+ *
+ * Full Order Book fields:
+ *
+ * {@link Order#getOrderId() orderId} - always empty
+ * {@link Order#getTradeId()} tradeId} - if available
+ * {@link Order#getTradeSize() tradeSize} and {@link Order#getTradePrice() tradePrice} -
+ * contain size and price of this trade - always present
+ *
+ */
+ TRADE(7),
+
+ /**
+ * Prior Trade/Order Execution bust.
+ * Order's {@link Order#getSize() size} and {@link Order#getPrice() price} will be equals to 0.
+ *
+ * Full Order Book fields:
+ *
+ * {@link Order#getOrderId() orderId} - always empty
+ * {@link Order#getTradeId() tradeId} - always present
+ * {@link Order#getTradeSize() tradeSize} and {@link Order#getTradePrice() tradePrice} - always empty
+ *
+ */
+ BUST(8);
+
+ private static final OrderAction[] ACTIONS = Util.buildEnumArrayByOrdinal(UNDEFINED, 9);
+
+ /**
+ * Returns side by integer code bit pattern.
+ * @param code integer code.
+ * @return side.
+ * @throws ArrayIndexOutOfBoundsException if code is invalid.
+ */
+ public static OrderAction valueOf(int code) {
+ return ACTIONS[code];
+ }
+
+ private final int code;
+
+ private OrderAction(int code) {
+ this.code = code;
+ if (code != ordinal())
+ throw new IllegalArgumentException("code differs from ordinal");
+ }
+
+ /**
+ * Returns integer code that is used in flag bits.
+ * @return integer code.
+ */
+ public int getCode() {
+ return code;
+ }
+}
diff --git a/dxfeed-api/src/main/java/com/dxfeed/event/market/OrderBase.java b/dxfeed-api/src/main/java/com/dxfeed/event/market/OrderBase.java
index fb56d17fc..e68a6b329 100644
--- a/dxfeed-api/src/main/java/com/dxfeed/event/market/OrderBase.java
+++ b/dxfeed-api/src/main/java/com/dxfeed/event/market/OrderBase.java
@@ -85,8 +85,9 @@
* Use the source code of {@link AbstractIndexedEventModel} for clarification on transactions and snapshot logic.
*/
@XmlType(propOrder = {
- "eventFlags", "index", "time", "timeNanoPart", "sequence", "source",
- "price", "sizeAsDouble", "executedSize", "count", "exchangeCode", "orderSide", "scope"
+ "eventFlags", "index", "time", "timeNanoPart", "sequence", "source", "action", "actionTime",
+ "orderId", "auxOrderId", "price", "sizeAsDouble", "executedSize", "count", "exchangeCode", "orderSide", "scope",
+ "tradeId", "tradePrice", "tradeSize"
})
public class OrderBase extends MarketEvent implements IndexedEvent {
private static final long serialVersionUID = 3;
@@ -148,12 +149,16 @@ public class OrderBase extends MarketEvent implements IndexedEvent {
/*
* Flags property has several significant bits that are packed into an integer in the following way:
- * 31..11 10...4 3 2 1 0
- * +--------+--------+----+----+----+----+
- * | |Exchange| Side | Scope |
- * +--------+--------+----+----+----+----+
+ * 31..15 14..11 10..4 3 2 1 0
+ * +--------+--------+--------+----+----+----+----+
+ * | | Action |Exchange| Side | Scope |
+ * +--------+--------+--------+----+----+----+----+
*/
+ // ACTION values are taken from OrderAction enum.
+ static final int ACTION_MASK = 0x0f;
+ static final int ACTION_SHIFT = 11;
+
// EXCHANGE values are ASCII chars in [0, 127].
static final int EXCHANGE_MASK = 0x7f;
static final int EXCHANGE_SHIFT = 4;
@@ -197,12 +202,21 @@ public class OrderBase extends MarketEvent implements IndexedEvent {
private long index;
private long timeSequence;
private int timeNanoPart;
+
+ private long actionTime;
+ private long orderId;
+ private long auxOrderId;
+
private double price = Double.NaN;
private double size = Double.NaN;
private double executedSize = Double.NaN;
private long count;
private int flags;
+ private long tradeId;
+ private double tradePrice = Double.NaN;
+ private double tradeSize = Double.NaN;
+
/**
* Creates new order with default values.
*/
@@ -378,13 +392,90 @@ public long getTimeNanos() {
/**
* Changes time of this order.
* Time is measured in nanoseconds between the current time and midnight, January 1, 1970 UTC.
- * @param timeNanos time of this order in nanoseconds..
+ * @param timeNanos time of this order in nanoseconds.
*/
public void setTimeNanos(long timeNanos) {
setTime(TimeNanosUtil.getMillisFromNanos(timeNanos));
timeNanoPart = TimeNanosUtil.getNanoPartFromNanos(timeNanos);
}
+ /**
+ * Returns order action if available, otherwise - {@link OrderAction#UNDEFINED}.
+ * This field is a part of the FOB support.
+ * @return order action or {@link OrderAction#UNDEFINED}.
+ */
+ @XmlElement
+ public OrderAction getAction() {
+ return OrderAction.valueOf(Util.getBits(flags, ACTION_MASK, ACTION_SHIFT));
+ }
+
+ /**
+ * Changes action of this order.
+ * @param action side of this order.
+ */
+ public void setAction(OrderAction action) {
+ flags = Util.setBits(flags, ACTION_MASK, ACTION_SHIFT, action.getCode());
+ }
+
+ /**
+ * Returns time of the last {@link #getAction() action}.
+ *
This field is a part of the FOB support.
+ * @return time of the last order action.
+ */
+ @XmlJavaTypeAdapter(type=long.class, value=XmlTimeAdapter.class)
+ public long getActionTime() {
+ return actionTime;
+ }
+
+ /**
+ * Changes time of the last action
+ * @param actionTime last order action time.
+ */
+ public void setActionTime(long actionTime) {
+ this.actionTime = actionTime;
+ }
+
+ /**
+ * Returns order ID if available. Some actions ({@link OrderAction#TRADE},
+ * {@link OrderAction#BUST}) have no order ID since they are not related to any order in Order book.
+ *
This field is a part of the FOB support.
+ * @return order ID or 0 if not available.
+ */
+ public long getOrderId() {
+ return orderId;
+ }
+
+ /**
+ * Changes order ID.
+ * @param orderId order ID.
+ */
+ public void setOrderId(long orderId) {
+ this.orderId = orderId;
+ }
+
+ /**
+ * Returns auxiliary order ID if available:
+ *
+ * in {@link OrderAction#NEW} - ID of the order replaced by this new order
+ * in {@link OrderAction#DELETE} - ID of the order that replaces this deleted order
+ * in {@link OrderAction#PARTIAL} - ID of the aggressor order
+ * in {@link OrderAction#EXECUTE} - ID of the aggressor order
+ *
+ * This field is a part of the FOB support.
+ * @return auxiliary order ID or 0 if not available.
+ */
+ public long getAuxOrderId() {
+ return auxOrderId;
+ }
+
+ /**
+ * Changes auxiliary order ID.
+ * @param auxOrderId auxiliary order ID.
+ */
+ public void setAuxOrderId(long auxOrderId) {
+ this.auxOrderId = auxOrderId;
+ }
+
/**
* Returns price of this order.
* @return price of this order.
@@ -477,6 +568,57 @@ public void setCount(long count) {
this.count = count;
}
+ /**
+ * Returns trade (order execution) ID for events containing trade-related action.
+ *
This field is a part of the FOB support.
+ * @return trade ID or 0 if not available.
+ */
+ public long getTradeId() {
+ return tradeId;
+ }
+
+ /**
+ * Changes trade ID.
+ * @param tradeId trade ID.
+ */
+ public void setTradeId(long tradeId) {
+ this.tradeId = tradeId;
+ }
+
+ /**
+ * Returns trade price for events containing trade-related action.
+ *
This field is a part of the FOB support.
+ * @return trade price of this action.
+ */
+ public double getTradePrice() {
+ return tradePrice;
+ }
+
+ /**
+ * Changes trade price.
+ * @param tradePrice trade price.
+ */
+ public void setTradePrice(double tradePrice) {
+ this.tradePrice = tradePrice;
+ }
+
+ /**
+ * Returns trade size for events containing trade-related action.
+ *
This field is a part of the FOB support.
+ * @return trade size.
+ */
+ public double getTradeSize() {
+ return tradeSize;
+ }
+
+ /**
+ * Changes trade size.
+ * @param tradeSize trade size.
+ */
+ public void setTradeSize(double tradeSize) {
+ this.tradeSize = tradeSize;
+ }
+
/**
* Returns exchange code of this order.
* @return exchange code of this order.
@@ -594,13 +736,20 @@ String baseFieldsToString() {
", time=" + TimeFormat.DEFAULT.withMillis().format(getTime()) +
", sequence=" + getSequence() +
", timeNanoPart=" + timeNanoPart +
+ ", action=" + getAction() +
+ ", actionTime=" + TimeFormat.DEFAULT.withMillis().format(actionTime) +
+ ", orderId=" + orderId +
+ ", auxOrderId=" + auxOrderId +
", price=" + price +
", size=" + size +
", executedSize=" + executedSize +
", count=" + count +
", exchange=" + Util.encodeChar(getExchangeCode()) +
", side=" + getOrderSide() +
- ", scope=" + getScope();
+ ", scope=" + getScope() +
+ ", tradeId=" + tradeId +
+ ", tradePrice=" + tradePrice +
+ ", tradeSize=" + tradeSize;
}
// ========================= package private access for delegate =========================
diff --git a/dxfeed-api/src/main/java/com/dxfeed/event/market/OrderSource.java b/dxfeed-api/src/main/java/com/dxfeed/event/market/OrderSource.java
index ebec7baca..7e2036fe7 100644
--- a/dxfeed-api/src/main/java/com/dxfeed/event/market/OrderSource.java
+++ b/dxfeed-api/src/main/java/com/dxfeed/event/market/OrderSource.java
@@ -48,11 +48,13 @@ public final class OrderSource extends IndexedEventSource {
private static final int TYPE_ORDER = 0;
private static final int TYPE_ANALYTIC_ORDER = 1;
private static final int TYPE_SPREAD_ORDER = 2;
- private static final int N_TYPES = 3;
+ private static final int FLAG_FULL_ORDER_BOOK = 3;
+ private static final int N_TYPES = 4;
private static final int PUB_ORDER = 1 << TYPE_ORDER;
private static final int PUB_ANALYTIC_ORDER = 1 << TYPE_ANALYTIC_ORDER;
private static final int PUB_SPREAD_ORDER = 1 << TYPE_SPREAD_ORDER;
+ private static final int FULL_ORDER_BOOK = 1 << FLAG_FULL_ORDER_BOOK;
@SuppressWarnings("unchecked")
private static final List[] PUBLISHABLE_LISTS = new List[N_TYPES];
@@ -117,10 +119,11 @@ public final class OrderSource extends IndexedEventSource {
/**
* Default source for publishing custom order books.
- * {@link Order}, {@link AnalyticOrder} and {@link SpreadOrder} events are {@link #isPublishable(Class) publishable} on this
- * source and the corresponding subscription can be observed via {@link DXPublisher}.
+ * {@link Order}, {@link AnalyticOrder} and {@link SpreadOrder} events are {@link #isPublishable(Class) publishable}
+ * on this source and the corresponding subscription can be observed via {@link DXPublisher}.
*/
- public static final OrderSource DEFAULT = new OrderSource(0, "DEFAULT", PUB_ORDER | PUB_ANALYTIC_ORDER | PUB_SPREAD_ORDER);
+ public static final OrderSource DEFAULT = new OrderSource(0, "DEFAULT",
+ PUB_ORDER | PUB_ANALYTIC_ORDER | PUB_SPREAD_ORDER | FLAG_FULL_ORDER_BOOK);
// ======== BEGIN: Custom OrderSource definitions ========
@@ -132,7 +135,7 @@ public final class OrderSource extends IndexedEventSource {
* {@link Order} events are {@link #isPublishable(Class) publishable} on this
* source and the corresponding subscription can be observed via {@link DXPublisher}.
*/
- public static final OrderSource NTV = new OrderSource("NTV", PUB_ORDER);
+ public static final OrderSource NTV = new OrderSource("NTV", PUB_ORDER | FULL_ORDER_BOOK);
/**
* NASDAQ Total View. Record for price level book.
@@ -302,6 +305,13 @@ public final class OrderSource extends IndexedEventSource {
*/
public static final OrderSource CFE = new OrderSource("CFE", PUB_ORDER);
+ /**
+ * CBOE Options C2 Exchange.
+ * {@link Order} events are {@link #isPublishable(Class) publishable} on this
+ * source and the corresponding subscription can be observed via {@link DXPublisher}.
+ */
+ public static final OrderSource C2OX = new OrderSource("C2OX", PUB_ORDER);
+
/**
* Small Exchange.
* {@link Order} events are {@link #isPublishable(Class) publishable} on this
@@ -369,6 +379,15 @@ public static List publishable(Class extends OrderBase> eventType
return PUBLISHABLE_VIEWS[getEventTypeId(eventType)];
}
+ /**
+ * Returns a list of publishable order sources that support Full Order Book.
+ *
+ * @return a list of publishable order sources that support Full Order Book.
+ */
+ public static List fullOrderBook() {
+ return PUBLISHABLE_VIEWS[FLAG_FULL_ORDER_BOOK];
+ }
+
// ========================= private instance fields =========================
private final int pubFlags;
@@ -399,6 +418,10 @@ private OrderSource(int id, String name, int pubFlags) {
if (!SOURCES_BY_NAME.add(this))
throw new IllegalArgumentException("duplicate name");
+ // Flag FULL_ORDER_BOOK requires that source must be publishable
+ if ((pubFlags & FULL_ORDER_BOOK) != 0 && (pubFlags & (PUB_ORDER | PUB_ANALYTIC_ORDER | PUB_SPREAD_ORDER)) == 0)
+ throw new IllegalArgumentException("unpublishable full order book order");
+
CACHE_SIZE = Math.max(CACHE_SIZE, SOURCES_BY_ID.size() * 4);
for (int i = 0; i < N_TYPES; i++) {
@@ -431,6 +454,15 @@ public boolean isPublishable(Class extends OrderBase> eventType) {
return (pubFlags & (1 << getEventTypeId(eventType))) != 0;
}
+ /**
+ * Returns {@code true} if this source supports Full Order Book.
+ *
+ * @return {@code true} if this source supports Full Order Book.
+ */
+ public boolean isFullOrderBook() {
+ return (pubFlags & FULL_ORDER_BOOK) != 0;
+ }
+
// ========================= private helper methods =========================
private static void checkChar(char c) {
diff --git a/dxfeed-api/src/main/java/com/dxfeed/event/market/SpreadOrder.java b/dxfeed-api/src/main/java/com/dxfeed/event/market/SpreadOrder.java
index d8747f046..6f2bf1fd6 100644
--- a/dxfeed-api/src/main/java/com/dxfeed/event/market/SpreadOrder.java
+++ b/dxfeed-api/src/main/java/com/dxfeed/event/market/SpreadOrder.java
@@ -94,6 +94,19 @@
*
* This event type cannot be used with {@link DXFeed#getLastEvent DXFeed.getLastEvent} method.
*
+ *
+ *
+ * Some feeds provide support for "Full Order Book" (FOB) where additional fields will be available:
+ *
+ * {@link #getAction() action} - event business meaning (see {@link OrderAction} for more details)
+ * {@link #getActionTime() actionTime} - time of the last action
+ * {@link #getOrderId() orderId} - ID of this order
+ * {@link #getAuxOrderId() auxOrderId} - additional ID for this order
+ * {@link #getTradeId() tradeId} - trade (order execution) ID
+ * {@link #getTradePrice() tradePrice} - price of the trade
+ * {@link #getTradeSize() tradeSize} - size of the trade
+ *
+ *
* Implementation details
*
* This event is implemented on top of QDS records {@code SpreadOrder#},
diff --git a/dxfeed-bin/pom.xml b/dxfeed-bin/pom.xml
index 580f4a501..f30cadff3 100644
--- a/dxfeed-bin/pom.xml
+++ b/dxfeed-bin/pom.xml
@@ -14,7 +14,7 @@
QD
com.devexperts.qd
- 3.291
+ 3.292
../pom.xml
4.0.0
diff --git a/dxfeed-codegen-verify/pom.xml b/dxfeed-codegen-verify/pom.xml
index 6b9c97c67..f80d92335 100644
--- a/dxfeed-codegen-verify/pom.xml
+++ b/dxfeed-codegen-verify/pom.xml
@@ -14,7 +14,7 @@
QD
com.devexperts.qd
- 3.291
+ 3.292
4.0.0
diff --git a/dxfeed-codegen/pom.xml b/dxfeed-codegen/pom.xml
index efa68665b..779492fa1 100644
--- a/dxfeed-codegen/pom.xml
+++ b/dxfeed-codegen/pom.xml
@@ -14,7 +14,7 @@
QD
com.devexperts.qd
- 3.291
+ 3.292
../pom.xml
4.0.0
diff --git a/dxfeed-codegen/src/main/java/com/dxfeed/api/codegen/DelegateGen.java b/dxfeed-codegen/src/main/java/com/dxfeed/api/codegen/DelegateGen.java
index f31f4f6db..d87650413 100644
--- a/dxfeed-codegen/src/main/java/com/dxfeed/api/codegen/DelegateGen.java
+++ b/dxfeed-codegen/src/main/java/com/dxfeed/api/codegen/DelegateGen.java
@@ -192,14 +192,28 @@ DelegateGen field(String fieldName, FieldType fieldType) {
DelegateGen phantom(String phantomProperty) {
if (fieldMappings.isEmpty()) {
+ // Phantom record
record.phantomProperty = phantomProperty;
mappingGen.phantom = true;
} else {
- lastFieldMapping().field.phantomProperty = phantomProperty;
+ // Phantom field
+ RecordField field = lastFieldMapping().field;
+ field.conditionalProperty = phantomProperty;
+ field.isPhantom = true;
}
return this;
}
+ // implies optional
+ DelegateGen onlyIf(String conditionalProperty) {
+ // Conditional field
+ RecordField field = lastFieldMapping().field;
+ field.conditionalProperty = conditionalProperty;
+ field.isPhantom = false;
+ field.required = false;
+ return this;
+ }
+
DelegateGen internal() {
lastFieldMapping().internal = true;
return this;
@@ -284,7 +298,7 @@ DelegateGen mapTimeAndSequence() {
}
DelegateGen mapTimeAndSequence(String timeFieldName, String sequenceFieldName) {
- map("Time", timeFieldName, FieldType.TIME).internal();
+ map("Time", timeFieldName, FieldType.TIME_SECONDS).internal();
map("Sequence", sequenceFieldName, FieldType.SEQUENCE).internal();
assign("TimeSequence", "(((long)#Time.Seconds#) << 32) | (#Sequence# & 0xFFFFFFFFL)");
injectPutEventCode(
@@ -299,7 +313,7 @@ DelegateGen mapTimeAndSequenceToIndex() {
}
DelegateGen mapTimeAndSequenceToIndex(String timeFieldName, String sequenceFieldName) {
- map("Time", timeFieldName, FieldType.TIME).time(0).internal();
+ map("Time", timeFieldName, FieldType.TIME_SECONDS).time(0).internal();
map("Sequence", sequenceFieldName, FieldType.SEQUENCE).time(1).internal();
assign("Index", "(((long)#Time.Seconds#) << 32) | (#Sequence# & 0xFFFFFFFFL)");
injectPutEventCode(
diff --git a/dxfeed-codegen/src/main/java/com/dxfeed/api/codegen/FactoryImplGen.java b/dxfeed-codegen/src/main/java/com/dxfeed/api/codegen/FactoryImplGen.java
index 2d8e5bfeb..f79064f3d 100644
--- a/dxfeed-codegen/src/main/java/com/dxfeed/api/codegen/FactoryImplGen.java
+++ b/dxfeed-codegen/src/main/java/com/dxfeed/api/codegen/FactoryImplGen.java
@@ -119,22 +119,26 @@ private void generateBuildSchemeCode(ClassGen cg) {
private void generateFieldCode(ClassGen cg, Map fields, String recordNameReference,
boolean isRegional)
{
- String phantomProperty = null;
+ String conditionalProperty = null;
+ boolean isPhantom = false;
for (Map.Entry fieldEntry : fields.entrySet()) {
String fieldName = fieldEntry.getKey();
RecordField f = fieldEntry.getValue();
if (isRegional && f.isCompositeOnly)
continue;
- if (phantomProperty != null && !phantomProperty.equals(f.phantomProperty)) {
+ if (conditionalProperty != null &&
+ (!conditionalProperty.equals(f.conditionalProperty) || isPhantom != f.isPhantom))
+ {
cg.unindent();
cg.code("}");
- phantomProperty = null;
+ conditionalProperty = null;
}
- if (f.phantomProperty != null && !f.phantomProperty.equals(phantomProperty)) {
+ if (f.conditionalProperty != null && !f.conditionalProperty.equals(conditionalProperty)) {
cg.addImport(new ClassName(SystemProperties.class));
- cg.code("if (SystemProperties.getBooleanProperty(\"" + f.phantomProperty + "\", false)) {");
+ cg.code("if (SystemProperties.getBooleanProperty(\"" + f.conditionalProperty + "\", false)) {");
cg.indent();
- phantomProperty = f.phantomProperty;
+ conditionalProperty = f.conditionalProperty;
+ isPhantom = f.isPhantom;
}
if (f.onlySuffixesDefault != null || f.exceptSuffixes != null) {
cg.code("if (" +
@@ -182,7 +186,7 @@ private void generateFieldCode(ClassGen cg, Map fields, Str
if (f.onlySuffixesDefault != null || f.exceptSuffixes != null)
cg.unindent();
}
- if (phantomProperty != null) {
+ if (conditionalProperty != null) {
cg.unindent();
cg.code("}");
}
diff --git a/dxfeed-codegen/src/main/java/com/dxfeed/api/codegen/FieldType.java b/dxfeed-codegen/src/main/java/com/dxfeed/api/codegen/FieldType.java
index 37156d023..5c9d2259f 100644
--- a/dxfeed-codegen/src/main/java/com/dxfeed/api/codegen/FieldType.java
+++ b/dxfeed-codegen/src/main/java/com/dxfeed/api/codegen/FieldType.java
@@ -30,8 +30,15 @@ enum FieldType {
.addAccess(Access.createWithAccessPattern("", "int", "0", "getInt(cursor, %s)", "setInt(cursor, %s, %s)"))
.setMapper(new DecimalMapper(int.class))
),
- TIME(new Builder()
- .addField(new Field(false, SerialFieldType.TIME))
+ TIME_MILLIS(new Builder()
+ .addField(new Field(false, SerialFieldType.TIME_MILLIS))
+ .addAccess(Access.createWithAccessPattern("Millis", "long", "0", "getLong(cursor, %s)", "setLong(cursor, %s, %s)"))
+ .addAccess(Access.createWithAccessPattern("Seconds", "int", "0", "TimeUtil.getSecondsFromTime(getLong(cursor, %s))", "setLong(cursor, %s, %s * 1000L)"))
+ .addImport(new ClassName(TimeUtil.class))
+ .setMapper(new DefaultMapper("Millis", long.class))
+ ),
+ TIME_SECONDS(new Builder()
+ .addField(new Field(false, SerialFieldType.TIME_SECONDS))
.addAccess(Access.createWithAccessPattern("Millis", "long", "0", "getInt(cursor, %s) * 1000L", "setInt(cursor, %s, TimeUtil.getSecondsFromTime(%s))"))
.addAccess(Access.createWithAccessPattern("Seconds", "int", "0", "getInt(cursor, %s)", "setInt(cursor, %s, %s)"))
.addImport(new ClassName(TimeUtil.class))
@@ -57,6 +64,11 @@ enum FieldType {
.addAccess(Access.createWithAccessPattern("", "int", "0", "getInt(cursor, %s)", "setInt(cursor, %s, %s)"))
.setMapper(new DefaultMapper(int.class))
),
+ LONG(new Builder()
+ .addField(new Field(false, SerialFieldType.LONG))
+ .addAccess(Access.createWithAccessPattern("", "long", "0", "getLong(cursor, %s)", "setLong(cursor, %s, %s)"))
+ .setMapper(new DefaultMapper(long.class))
+ ),
INT(new Builder()
.addField(new Field(false, SerialFieldType.COMPACT_INT))
.addAccess(Access.createWithAccessPattern("", "int", "0", "getInt(cursor, %s)", "setInt(cursor, %s, %s)"))
@@ -202,6 +214,8 @@ static final class Field {
final boolean isObject;
final SerialFieldType serialType;
final boolean adaptiveDecimal;
+ //FIXME
+ //final boolean adaptiveTime; // no selectors
final String[] typeSelectors;
final String suffix;
diff --git a/dxfeed-codegen/src/main/java/com/dxfeed/api/codegen/ImplCodeGen.java b/dxfeed-codegen/src/main/java/com/dxfeed/api/codegen/ImplCodeGen.java
index 8fb0f5082..0d72f2663 100644
--- a/dxfeed-codegen/src/main/java/com/dxfeed/api/codegen/ImplCodeGen.java
+++ b/dxfeed-codegen/src/main/java/com/dxfeed/api/codegen/ImplCodeGen.java
@@ -65,6 +65,10 @@ public class ImplCodeGen {
"1hour|2hour|3hour|4hour|6hour|8hour|12hour|Day|2Day|3Day|4Day|Week|Month|OptExp";
private static final String BID_ASK_VOLUME_SUFFIXES = ".*[{,]price=(bid|ask|mark|s)[,}].*";
+ private static final String DXSCHEME_FOB = "dxscheme.fob";
+ private static final String FOB_SUFFIX_PROPERTY = "com.dxfeed.event.market.impl.Order.fob.suffixes";
+ private static final String FOB_SUFFIX_DEFAULT = getFullOrderBookSuffixes();
+
public static void main(String[] args) throws IOException {
new ImplCodeGen("", false).run();
}
@@ -92,11 +96,11 @@ public void runForDxfeedImpl() throws IOException {
inheritMappingFrom(MARKET_EVENT_MAPPING).
map("Sequence", FieldType.SEQUENCE).optional().disabledByDefault().internal(). // assign after bid/ask time
map("TimeNanoPart", FieldType.TIME_NANO_PART).optional().disabledByDefault().
- map("BidTime", "Bid.Time", FieldType.TIME).optional().
+ map("BidTime", "Bid.Time", FieldType.TIME_SECONDS).optional().
map("BidExchangeCode", "Bid.Exchange", FieldType.CHAR).alt("recordExchange").compositeOnly().optional().
map("BidPrice", "Bid.Price", FieldType.PRICE).
map("BidSize", "Bid.Size", FieldType.SIZE).
- map("AskTime", "Ask.Time", FieldType.TIME).optional().
+ map("AskTime", "Ask.Time", FieldType.TIME_SECONDS).optional().
map("AskExchangeCode", "Ask.Exchange", FieldType.CHAR).alt("recordExchange").compositeOnly().optional().
map("AskPrice", "Ask.Price", FieldType.PRICE).
map("AskSize", "Ask.Size", FieldType.SIZE).
@@ -111,10 +115,10 @@ public void runForDxfeedImpl() throws IOException {
field("BidSize", "Bid.Size", FieldType.DECIMAL_AS_DOUBLE).
field("AskPrice", "Ask.Price", FieldType.DECIMAL_AS_DOUBLE).
field("AskSize", "Ask.Size", FieldType.DECIMAL_AS_DOUBLE).
- field("BidPriceTimestamp", "Bid.Price.Timestamp", FieldType.TIME).
- field("BidSizeTimestamp", "Bid.Size.Timestamp", FieldType.TIME).
- field("AskPriceTimestamp", "Ask.Price.Timestamp", FieldType.TIME).
- field("AskSizeTimestamp", "Ask.Size.Timestamp", FieldType.TIME);
+ field("BidPriceTimestamp", "Bid.Price.Timestamp", FieldType.TIME_SECONDS).
+ field("BidSizeTimestamp", "Bid.Size.Timestamp", FieldType.TIME_SECONDS).
+ field("AskPriceTimestamp", "Ask.Price.Timestamp", FieldType.TIME_SECONDS).
+ field("AskSizeTimestamp", "Ask.Size.Timestamp", FieldType.TIME_SECONDS);
ctx.delegate("Trade", Trade.class, "Trade&").
inheritDelegateFrom(MARKET_EVENT_DELEGATE).
@@ -190,7 +194,7 @@ public void runForDxfeedImpl() throws IOException {
exchanges("I", true). // generate only Book&I by default
field("Id", "ID", FieldType.INDEX).time(0).
field("Sequence", FieldType.VOID).time(1).
- field("Time", FieldType.TIME).
+ field("Time", FieldType.TIME_SECONDS).
field("Type", FieldType.CHAR).
field("Price", FieldType.PRICE).
field("Size", FieldType.SIZE).
@@ -211,8 +215,8 @@ public void runForDxfeedImpl() throws IOException {
field("FreeFloat", FieldType.DECIMAL_AS_LONG).optional().
map("HighLimitPrice", "HighLimitPrice", FieldType.PRICE).optional().
map("LowLimitPrice", "LowLimitPrice", FieldType.PRICE).optional().
- map("HaltStartTime", "Halt.StartTime", FieldType.TIME).optional().
- map("HaltEndTime", "Halt.EndTime", FieldType.TIME).optional().
+ map("HaltStartTime", "Halt.StartTime", FieldType.TIME_SECONDS).optional().
+ map("HaltEndTime", "Halt.EndTime", FieldType.TIME_SECONDS).optional().
map("Flags", "Flags", FieldType.FLAGS).optional().
map("Description", "Description", FieldType.STRING).
map("StatusReason", "StatusReason", FieldType.STRING).optional().
@@ -233,11 +237,17 @@ public void runForDxfeedImpl() throws IOException {
).
mapTimeAndSequence().
map("TimeNanoPart", "TimeNanoPart", FieldType.TIME_NANO_PART).optional().disabledByDefault().
+ map("ActionTime", "ActionTime", FieldType.TIME_MILLIS).onlyIf(DXSCHEME_FOB).onlySuffixes(FOB_SUFFIX_PROPERTY, FOB_SUFFIX_DEFAULT).
+ map("OrderId", "OrderId", FieldType.LONG).onlyIf(DXSCHEME_FOB).onlySuffixes(FOB_SUFFIX_PROPERTY, FOB_SUFFIX_DEFAULT).
+ map("AuxOrderId", "AuxOrderId", FieldType.LONG).onlyIf(DXSCHEME_FOB).onlySuffixes(FOB_SUFFIX_PROPERTY, FOB_SUFFIX_DEFAULT).
map("Price", "Price", FieldType.PRICE).
map("Size", "Size", FieldType.SIZE).
map("ExecutedSize", "ExecutedSize", FieldType.DECIMAL_AS_DOUBLE).optional().disabledByDefault().
map("Count", "Count", FieldType.INT_DECIMAL).onlySuffixes("com.dxfeed.event.order.impl.Order.suffixes.count", "").
map("Flags", "Flags", FieldType.FLAGS).
+ map("TradeId", "TradeId", FieldType.LONG).onlyIf(DXSCHEME_FOB).onlySuffixes(FOB_SUFFIX_PROPERTY, FOB_SUFFIX_DEFAULT).
+ map("TradePrice", "TradePrice", FieldType.DECIMAL_AS_DOUBLE).onlyIf(DXSCHEME_FOB).onlySuffixes(FOB_SUFFIX_PROPERTY, FOB_SUFFIX_DEFAULT).
+ map("TradeSize", "TradeSize", FieldType.DECIMAL_AS_DOUBLE).onlyIf(DXSCHEME_FOB).onlySuffixes(FOB_SUFFIX_PROPERTY, FOB_SUFFIX_DEFAULT).
map("MarketMaker", "MMID", FieldType.SHORT_STRING).onlySuffixes(
"com.dxfeed.event.order.impl.Order.suffixes.mmid", "|#NTV|#BATE|#CHIX|#CEUX|#BXTR").
field("IcebergPeakSize", "IcebergPeakSize", FieldType.DECIMAL_AS_DOUBLE).optional().disabledByDefault().
@@ -269,11 +279,17 @@ public void runForDxfeedImpl() throws IOException {
).
mapTimeAndSequence().
map("TimeNanoPart", "TimeNanoPart", FieldType.TIME_NANO_PART).optional().disabledByDefault().
+ map("ActionTime", "ActionTime", FieldType.TIME_MILLIS).onlyIf(DXSCHEME_FOB).onlySuffixes(FOB_SUFFIX_PROPERTY, FOB_SUFFIX_DEFAULT).
+ map("OrderId", "OrderId", FieldType.LONG).onlyIf(DXSCHEME_FOB).onlySuffixes(FOB_SUFFIX_PROPERTY, FOB_SUFFIX_DEFAULT).
+ map("AuxOrderId", "AuxOrderId", FieldType.LONG).onlyIf(DXSCHEME_FOB).onlySuffixes(FOB_SUFFIX_PROPERTY, FOB_SUFFIX_DEFAULT).
map("Price", "Price", FieldType.PRICE).
map("Size", "Size", FieldType.SIZE).
map("ExecutedSize", "ExecutedSize", FieldType.DECIMAL_AS_DOUBLE).optional().disabledByDefault().
map("Count", "Count", FieldType.INT_DECIMAL).onlySuffixes("com.dxfeed.event.order.impl.AnalyticOrder.suffixes.count", "").
map("Flags", "Flags", FieldType.FLAGS).
+ map("TradeId", "TradeId", FieldType.LONG).onlyIf(DXSCHEME_FOB).onlySuffixes(FOB_SUFFIX_PROPERTY, FOB_SUFFIX_DEFAULT).
+ map("TradePrice", "TradePrice", FieldType.DECIMAL_AS_DOUBLE).onlyIf(DXSCHEME_FOB).onlySuffixes(FOB_SUFFIX_PROPERTY, FOB_SUFFIX_DEFAULT).
+ map("TradeSize", "TradeSize", FieldType.DECIMAL_AS_DOUBLE).onlyIf(DXSCHEME_FOB).onlySuffixes(FOB_SUFFIX_PROPERTY, FOB_SUFFIX_DEFAULT).
map("MarketMaker", "MMID", FieldType.SHORT_STRING).onlySuffixes(
"com.dxfeed.event.order.impl.AnalyticOrder.suffixes.mmid", "|#NTV|#BATE|#CHIX|#CEUX|#BXTR").
map("IcebergPeakSize", "IcebergPeakSize", FieldType.DECIMAL_AS_DOUBLE).optional().disabledByDefault().
@@ -305,11 +321,17 @@ public void runForDxfeedImpl() throws IOException {
).
mapTimeAndSequence().
map("TimeNanoPart", "TimeNanoPart", FieldType.TIME_NANO_PART).optional().disabledByDefault().
+ map("ActionTime", "ActionTime", FieldType.TIME_MILLIS).onlyIf(DXSCHEME_FOB).onlySuffixes(FOB_SUFFIX_PROPERTY, FOB_SUFFIX_DEFAULT).
+ map("OrderId", "OrderId", FieldType.LONG).onlyIf(DXSCHEME_FOB).onlySuffixes(FOB_SUFFIX_PROPERTY, FOB_SUFFIX_DEFAULT).
+ map("AuxOrderId", "AuxOrderId", FieldType.LONG).onlyIf(DXSCHEME_FOB).onlySuffixes(FOB_SUFFIX_PROPERTY, FOB_SUFFIX_DEFAULT).
map("Price", "Price", FieldType.PRICE).
map("Size", "Size", FieldType.SIZE).
map("ExecutedSize", "ExecutedSize", FieldType.DECIMAL_AS_DOUBLE).optional().disabledByDefault().
map("Count", "Count", FieldType.INT_DECIMAL).onlySuffixes("com.dxfeed.event.order.impl.SpreadOrder.suffixes.count", "").
map("Flags", "Flags", FieldType.FLAGS).
+ map("TradeId", "TradeId", FieldType.LONG).onlyIf(DXSCHEME_FOB).onlySuffixes(FOB_SUFFIX_PROPERTY, FOB_SUFFIX_DEFAULT).
+ map("TradePrice", "TradePrice", FieldType.DECIMAL_AS_DOUBLE).onlyIf(DXSCHEME_FOB).onlySuffixes(FOB_SUFFIX_PROPERTY, FOB_SUFFIX_DEFAULT).
+ map("TradeSize", "TradeSize", FieldType.DECIMAL_AS_DOUBLE).onlyIf(DXSCHEME_FOB).onlySuffixes(FOB_SUFFIX_PROPERTY, FOB_SUFFIX_DEFAULT).
map("SpreadSymbol", "SpreadSymbol", FieldType.STRING).
injectPutEventCode(
"if (index < 0)",
@@ -326,7 +348,7 @@ public void runForDxfeedImpl() throws IOException {
subContract(QDContract.TICKER).
source("m.getRecordExchange() == 0 ? OrderSource.COMPOSITE_BID : OrderSource.REGIONAL_BID").
assign("Index", "((long)getSource().id() << 48) | ((long)m.getRecordExchange() << 32)").
- map("Time", "BidTime", "Bid.Time", FieldType.TIME).optional().
+ map("Time", "BidTime", "Bid.Time", FieldType.TIME_SECONDS).optional().
assign("Sequence", "0").
map("Price", "BidPrice", "Bid.Price", FieldType.PRICE).
map("Size", "BidSize", "Bid.Size", FieldType.SIZE).
@@ -341,7 +363,7 @@ public void runForDxfeedImpl() throws IOException {
subContract(QDContract.TICKER).
source("m.getRecordExchange() == 0 ? OrderSource.COMPOSITE_ASK : OrderSource.REGIONAL_ASK").
assign("Index", "((long)getSource().id() << 48) | ((long)m.getRecordExchange() << 32)").
- map("Time", "AskTime", "Ask.Time", FieldType.TIME).optional().
+ map("Time", "AskTime", "Ask.Time", FieldType.TIME_SECONDS).optional().
assign("Sequence", "0").
map("Price", "AskPrice", "Ask.Price", FieldType.PRICE).
map("Size", "AskSize", "Ask.Size", FieldType.SIZE).
@@ -359,7 +381,7 @@ public void runForDxfeedImpl() throws IOException {
assign("Index", "((long)getSource().id() << 48) | ((long)#ExchangeCode# << 32) | (#MarketMaker# & 0xFFFFFFFFL)").
map("ExchangeCode", "MMExchange", FieldType.CHAR).time(0).
map("MarketMaker", "MMID", FieldType.SHORT_STRING).time(1).
- map("Time", "BidTime", "MMBid.Time", FieldType.TIME).optional().
+ map("Time", "BidTime", "MMBid.Time", FieldType.TIME_SECONDS).optional().
assign("Sequence", "0").
map("Price", "BidPrice", "MMBid.Price", FieldType.PRICE).
map("Size", "BidSize", "MMBid.Size", FieldType.SIZE).
@@ -374,7 +396,7 @@ public void runForDxfeedImpl() throws IOException {
assign("Index", "((long)getSource().id() << 48) | ((long)#ExchangeCode# << 32) | (#MarketMaker# & 0xFFFFFFFFL)").
map("ExchangeCode", "MMExchange", FieldType.CHAR).time(0).
map("MarketMaker", "MMID", FieldType.SHORT_STRING).time(1).
- map("Time", "AskTime", "MMAsk.Time", FieldType.TIME).optional().
+ map("Time", "AskTime", "MMAsk.Time", FieldType.TIME_SECONDS).optional().
assign("Sequence", "0").
map("Price", "AskPrice", "MMAsk.Price", FieldType.PRICE).
map("Size", "AskSize", "MMAsk.Size", FieldType.SIZE).
@@ -439,7 +461,7 @@ public void runForDxfeedImpl() throws IOException {
// use common mapping for "Candle" record, just generate Trade records and bind them to Candle delegate
ctx.record("com.dxfeed.event.candle", Candle.class, "Candle", "Trade.").
suffixes(TRADE_RECORD_SUFFIXES).
- field("Time", FieldType.TIME).time(0).
+ field("Time", FieldType.TIME_SECONDS).time(0).
field("Sequence", FieldType.SEQUENCE).time(1).
field("Count", FieldType.DECIMAL_AS_LONG).optional().
field("Open", FieldType.PRICE).
@@ -552,4 +574,11 @@ private String getOrderSuffixes(Class extends OrderBase> eventType) {
map(orderSource -> "|#" + orderSource.name()).
collect(Collectors.joining());
}
+
+ private static String getFullOrderBookSuffixes() {
+ return OrderSource.fullOrderBook().stream().
+ filter(os -> !OrderSource.DEFAULT.equals(os) && !OrderSource.isSpecialSourceId(os.id())).
+ map(orderSource -> "|#" + orderSource.name()).
+ collect(Collectors.joining());
+ }
}
diff --git a/dxfeed-codegen/src/main/java/com/dxfeed/api/codegen/ImplCodeGenAnnotationProcessor.java b/dxfeed-codegen/src/main/java/com/dxfeed/api/codegen/ImplCodeGenAnnotationProcessor.java
index 8be247463..37141015f 100644
--- a/dxfeed-codegen/src/main/java/com/dxfeed/api/codegen/ImplCodeGenAnnotationProcessor.java
+++ b/dxfeed-codegen/src/main/java/com/dxfeed/api/codegen/ImplCodeGenAnnotationProcessor.java
@@ -236,8 +236,12 @@ private FieldType mapToFieldType(EventFieldType fieldSerializedType) {
case DATE: return FieldType.DATE;
case DECIMAL: return FieldType.DECIMAL_AS_DOUBLE;
case INT: return FieldType.INT;
+ case LONG: return FieldType.LONG;
case SHORT_STRING: return FieldType.SHORT_STRING;
- case TIME: return FieldType.TIME;
+ //noinspection deprecation
+ case TIME: return FieldType.TIME_SECONDS;
+ case TIME_SECONDS: return FieldType.TIME_SECONDS;
+ case TIME_MILLIS: return FieldType.TIME_MILLIS;
case STRING: return FieldType.STRING;
case MARSHALLED: return FieldType.MARSHALLED;
}
diff --git a/dxfeed-codegen/src/main/java/com/dxfeed/api/codegen/RecordField.java b/dxfeed-codegen/src/main/java/com/dxfeed/api/codegen/RecordField.java
index ac8663c9c..d4ca11112 100644
--- a/dxfeed-codegen/src/main/java/com/dxfeed/api/codegen/RecordField.java
+++ b/dxfeed-codegen/src/main/java/com/dxfeed/api/codegen/RecordField.java
@@ -36,7 +36,10 @@ class RecordField {
String onlySuffixesDefault;
String exceptSuffixes;
String voidSuffixes;
- String phantomProperty;
+ // Represents either phantom or conditional property
+ String conditionalProperty;
+ // Flag indicating phantom property
+ boolean isPhantom;
RecordField(String propertyName, String eventName, String fieldName, FieldType fieldType) {
this.propertyName = propertyName;
@@ -146,6 +149,6 @@ private String generateIndexesAreNotDefinedCondition() {
}
boolean isActive() {
- return phantomProperty == null && fieldType.accesses.size() > 0;
+ return !isPhantom && fieldType.accesses.size() > 0;
}
}
diff --git a/dxfeed-codegen/src/test/java/com/dxfeed/api/codegen/event/BetterQuote.java b/dxfeed-codegen/src/test/java/com/dxfeed/api/codegen/event/BetterQuote.java
index 63c37295b..2e0a02804 100644
--- a/dxfeed-codegen/src/test/java/com/dxfeed/api/codegen/event/BetterQuote.java
+++ b/dxfeed-codegen/src/test/java/com/dxfeed/api/codegen/event/BetterQuote.java
@@ -11,6 +11,8 @@
*/
package com.dxfeed.api.codegen.event;
+import com.dxfeed.annotation.EventFieldMapping;
+import com.dxfeed.annotation.EventFieldType;
import com.dxfeed.annotation.EventTypeMapping;
import com.dxfeed.event.market.Quote;
@@ -77,6 +79,7 @@ public void setBetterInt(int betterInt) {
this.betterInt = betterInt;
}
+ @EventFieldMapping(type = EventFieldType.LONG)
public long getBetterLong() {
return betterLong;
}
diff --git a/dxfeed-impl/pom.xml b/dxfeed-impl/pom.xml
index 3ff318b2b..6ecb2f9aa 100644
--- a/dxfeed-impl/pom.xml
+++ b/dxfeed-impl/pom.xml
@@ -14,7 +14,7 @@
QD
com.devexperts.qd
- 3.291
+ 3.292
4.0.0
diff --git a/dxfeed-impl/src/main/java/com/dxfeed/api/impl/EventDelegateFactory.java b/dxfeed-impl/src/main/java/com/dxfeed/api/impl/EventDelegateFactory.java
index 25d327b64..bcf8d5a7f 100644
--- a/dxfeed-impl/src/main/java/com/dxfeed/api/impl/EventDelegateFactory.java
+++ b/dxfeed-impl/src/main/java/com/dxfeed/api/impl/EventDelegateFactory.java
@@ -31,6 +31,7 @@ protected String getBaseRecordName(String recordName) {
return recordName;
}
+ //TODO rename to selectDecimal
protected SerialFieldType select(SerialFieldType type, String... typeSelectors) {
if ("true".equalsIgnoreCase(System.getProperty("dxscheme.wide")))
type = SerialFieldType.WIDE_DECIMAL;
@@ -45,4 +46,6 @@ protected SerialFieldType select(SerialFieldType type, String... typeSelectors)
}
return type;
}
+
+ //FIXME implement selectTime
}
diff --git a/dxfeed-impl/src/main/java/com/dxfeed/event/candle/CandleFactoryImpl.java b/dxfeed-impl/src/main/java/com/dxfeed/event/candle/CandleFactoryImpl.java
index 5b74c793e..87a9de34c 100644
--- a/dxfeed-impl/src/main/java/com/dxfeed/event/candle/CandleFactoryImpl.java
+++ b/dxfeed-impl/src/main/java/com/dxfeed/event/candle/CandleFactoryImpl.java
@@ -36,7 +36,7 @@ public final class CandleFactoryImpl extends EventDelegateFactory implements Rec
// BEGIN: CODE AUTOMATICALLY GENERATED: DO NOT MODIFY. IT IS REGENERATED BY com.dxfeed.api.codegen.ImplCodeGen
@Override
public void buildScheme(SchemeBuilder builder) {
- builder.addRequiredField("TradeHistory", "Time", SerialFieldType.TIME, SchemeFieldTime.FIRST_TIME_INT_FIELD);
+ builder.addRequiredField("TradeHistory", "Time", SerialFieldType.TIME_SECONDS, SchemeFieldTime.FIRST_TIME_INT_FIELD);
builder.addRequiredField("TradeHistory", "Sequence", SerialFieldType.SEQUENCE, SchemeFieldTime.SECOND_TIME_INT_FIELD);
builder.addOptionalField("TradeHistory", "Exchange", SerialFieldType.UTF_CHAR, "Candle", "ExchangeCode", true);
builder.addRequiredField("TradeHistory", "Price", select(SerialFieldType.DECIMAL, "dxscheme.price"));
@@ -46,7 +46,7 @@ public void buildScheme(SchemeBuilder builder) {
for (String suffix : SystemProperties.getProperty("com.dxfeed.event.candle.impl.Candle.suffixes", "").split("\\|")) {
String recordName = "Candle" + suffix;
- builder.addRequiredField(recordName, "Time", SerialFieldType.TIME, SchemeFieldTime.FIRST_TIME_INT_FIELD);
+ builder.addRequiredField(recordName, "Time", SerialFieldType.TIME_SECONDS, SchemeFieldTime.FIRST_TIME_INT_FIELD);
builder.addRequiredField(recordName, "Sequence", SerialFieldType.SEQUENCE, SchemeFieldTime.SECOND_TIME_INT_FIELD);
builder.addOptionalField(recordName, "Count", select(SerialFieldType.DECIMAL), "Candle", "Count", true);
builder.addRequiredField(recordName, "Open", select(SerialFieldType.DECIMAL, "dxscheme.price"));
@@ -65,7 +65,7 @@ public void buildScheme(SchemeBuilder builder) {
for (String suffix : SystemProperties.getProperty("com.dxfeed.event.candle.impl.Trade.suffixes", "133ticks|144ticks|233ticks|333ticks|400ticks|512ticks|1600ticks|3200ticks|1min|2min|3min|4min|5min|6min|10min|12min|15min|20min|30min|1hour|2hour|3hour|4hour|6hour|8hour|12hour|Day|2Day|3Day|4Day|Week|Month|OptExp").split("\\|")) {
String recordName = "Trade." + suffix;
- builder.addRequiredField(recordName, "Time", SerialFieldType.TIME, SchemeFieldTime.FIRST_TIME_INT_FIELD);
+ builder.addRequiredField(recordName, "Time", SerialFieldType.TIME_SECONDS, SchemeFieldTime.FIRST_TIME_INT_FIELD);
builder.addRequiredField(recordName, "Sequence", SerialFieldType.SEQUENCE, SchemeFieldTime.SECOND_TIME_INT_FIELD);
builder.addOptionalField(recordName, "Count", select(SerialFieldType.DECIMAL), "Candle", "Count", true);
builder.addRequiredField(recordName, "Open", select(SerialFieldType.DECIMAL, "dxscheme.price"));
diff --git a/dxfeed-impl/src/main/java/com/dxfeed/event/market/AnalyticOrderDelegate.java b/dxfeed-impl/src/main/java/com/dxfeed/event/market/AnalyticOrderDelegate.java
index a7f4259b4..0a081cee2 100644
--- a/dxfeed-impl/src/main/java/com/dxfeed/event/market/AnalyticOrderDelegate.java
+++ b/dxfeed-impl/src/main/java/com/dxfeed/event/market/AnalyticOrderDelegate.java
@@ -47,11 +47,17 @@ public AnalyticOrder getEvent(AnalyticOrder event, RecordCursor cursor) {
event.setIndex(((long) getSource().id() << 32) | (m.getIndex(cursor) & 0xFFFFFFFFL));
event.setTimeSequence((((long) m.getTimeSeconds(cursor)) << 32) | (m.getSequence(cursor) & 0xFFFFFFFFL));
event.setTimeNanoPart(m.getTimeNanoPart(cursor));
+ event.setActionTime(m.getActionTimeMillis(cursor));
+ event.setOrderId(m.getOrderId(cursor));
+ event.setAuxOrderId(m.getAuxOrderId(cursor));
event.setPrice(m.getPrice(cursor));
event.setSizeAsDouble(m.getSizeDouble(cursor));
event.setExecutedSize(m.getExecutedSize(cursor));
event.setCount(m.getCount(cursor));
event.setFlags(m.getFlags(cursor));
+ event.setTradeId(m.getTradeId(cursor));
+ event.setTradePrice(m.getTradePrice(cursor));
+ event.setTradeSize(m.getTradeSize(cursor));
event.setMarketMaker(m.getMarketMakerString(cursor));
event.setIcebergPeakSize(m.getIcebergPeakSize(cursor));
event.setIcebergHiddenSize(m.getIcebergHiddenSize(cursor));
@@ -69,11 +75,17 @@ public RecordCursor putEvent(AnalyticOrder event, RecordBuffer buf) {
m.setTimeSeconds(cursor, (int) (event.getTimeSequence() >>> 32));
m.setSequence(cursor, (int) event.getTimeSequence());
m.setTimeNanoPart(cursor, event.getTimeNanoPart());
+ m.setActionTimeMillis(cursor, event.getActionTime());
+ m.setOrderId(cursor, event.getOrderId());
+ m.setAuxOrderId(cursor, event.getAuxOrderId());
m.setPrice(cursor, event.getPrice());
m.setSizeDouble(cursor, event.getSizeAsDouble());
m.setExecutedSize(cursor, event.getExecutedSize());
m.setCount(cursor, (int) event.getCount());
m.setFlags(cursor, event.getFlags());
+ m.setTradeId(cursor, event.getTradeId());
+ m.setTradePrice(cursor, event.getTradePrice());
+ m.setTradeSize(cursor, event.getTradeSize());
m.setMarketMakerString(cursor, event.getMarketMaker());
m.setIcebergPeakSize(cursor, event.getIcebergPeakSize());
m.setIcebergHiddenSize(cursor, event.getIcebergHiddenSize());
diff --git a/dxfeed-impl/src/main/java/com/dxfeed/event/market/MarketFactoryImpl.java b/dxfeed-impl/src/main/java/com/dxfeed/event/market/MarketFactoryImpl.java
index a3a633855..61a5f6f43 100644
--- a/dxfeed-impl/src/main/java/com/dxfeed/event/market/MarketFactoryImpl.java
+++ b/dxfeed-impl/src/main/java/com/dxfeed/event/market/MarketFactoryImpl.java
@@ -48,11 +48,11 @@ public final class MarketFactoryImpl extends EventDelegateFactory implements Rec
public void buildScheme(SchemeBuilder builder) {
builder.addOptionalField("Quote", "Sequence", SerialFieldType.SEQUENCE, "Quote", "Sequence", false);
builder.addOptionalField("Quote", "TimeNanoPart", SerialFieldType.COMPACT_INT, "Quote", "TimeNanoPart", false);
- builder.addOptionalField("Quote", "Bid.Time", SerialFieldType.TIME, "Quote", "BidTime", true);
+ builder.addOptionalField("Quote", "Bid.Time", SerialFieldType.TIME_SECONDS, "Quote", "BidTime", true);
builder.addOptionalField("Quote", "Bid.Exchange", SerialFieldType.UTF_CHAR, "Quote", "BidExchangeCode", true);
builder.addRequiredField("Quote", "Bid.Price", select(SerialFieldType.DECIMAL, "dxscheme.price"));
builder.addRequiredField("Quote", "Bid.Size", select(SerialFieldType.COMPACT_INT, "dxscheme.size"));
- builder.addOptionalField("Quote", "Ask.Time", SerialFieldType.TIME, "Quote", "AskTime", true);
+ builder.addOptionalField("Quote", "Ask.Time", SerialFieldType.TIME_SECONDS, "Quote", "AskTime", true);
builder.addOptionalField("Quote", "Ask.Exchange", SerialFieldType.UTF_CHAR, "Quote", "AskExchangeCode", true);
builder.addRequiredField("Quote", "Ask.Price", select(SerialFieldType.DECIMAL, "dxscheme.price"));
builder.addRequiredField("Quote", "Ask.Size", select(SerialFieldType.COMPACT_INT, "dxscheme.size"));
@@ -60,10 +60,10 @@ public void buildScheme(SchemeBuilder builder) {
String recordName = "Quote&" + exchange;
builder.addOptionalField(recordName, "Sequence", SerialFieldType.SEQUENCE, "Quote", "Sequence", false);
builder.addOptionalField(recordName, "TimeNanoPart", SerialFieldType.COMPACT_INT, "Quote", "TimeNanoPart", false);
- builder.addOptionalField(recordName, "Bid.Time", SerialFieldType.TIME, "Quote", "BidTime", true);
+ builder.addOptionalField(recordName, "Bid.Time", SerialFieldType.TIME_SECONDS, "Quote", "BidTime", true);
builder.addRequiredField(recordName, "Bid.Price", select(SerialFieldType.DECIMAL, "dxscheme.price"));
builder.addRequiredField(recordName, "Bid.Size", select(SerialFieldType.COMPACT_INT, "dxscheme.size"));
- builder.addOptionalField(recordName, "Ask.Time", SerialFieldType.TIME, "Quote", "AskTime", true);
+ builder.addOptionalField(recordName, "Ask.Time", SerialFieldType.TIME_SECONDS, "Quote", "AskTime", true);
builder.addRequiredField(recordName, "Ask.Price", select(SerialFieldType.DECIMAL, "dxscheme.price"));
builder.addRequiredField(recordName, "Ask.Size", select(SerialFieldType.COMPACT_INT, "dxscheme.size"));
}
@@ -73,13 +73,13 @@ public void buildScheme(SchemeBuilder builder) {
builder.addRequiredField("Quote2", "Bid.Size", select(SerialFieldType.DECIMAL));
builder.addRequiredField("Quote2", "Ask.Price", select(SerialFieldType.DECIMAL));
builder.addRequiredField("Quote2", "Ask.Size", select(SerialFieldType.DECIMAL));
- builder.addRequiredField("Quote2", "Bid.Price.Timestamp", SerialFieldType.TIME);
- builder.addRequiredField("Quote2", "Bid.Size.Timestamp", SerialFieldType.TIME);
- builder.addRequiredField("Quote2", "Ask.Price.Timestamp", SerialFieldType.TIME);
- builder.addRequiredField("Quote2", "Ask.Size.Timestamp", SerialFieldType.TIME);
+ builder.addRequiredField("Quote2", "Bid.Price.Timestamp", SerialFieldType.TIME_SECONDS);
+ builder.addRequiredField("Quote2", "Bid.Size.Timestamp", SerialFieldType.TIME_SECONDS);
+ builder.addRequiredField("Quote2", "Ask.Price.Timestamp", SerialFieldType.TIME_SECONDS);
+ builder.addRequiredField("Quote2", "Ask.Size.Timestamp", SerialFieldType.TIME_SECONDS);
}
- builder.addOptionalField("Trade", "Last.Time", SerialFieldType.TIME, "Trade", "Time", true);
+ builder.addOptionalField("Trade", "Last.Time", SerialFieldType.TIME_SECONDS, "Trade", "Time", true);
builder.addOptionalField("Trade", "Last.Sequence", SerialFieldType.SEQUENCE, "Trade", "Sequence", true);
builder.addOptionalField("Trade", "Last.TimeNanoPart", SerialFieldType.COMPACT_INT, "Trade", "TimeNanoPart", false);
builder.addOptionalField("Trade", "Last.Exchange", SerialFieldType.UTF_CHAR, "Trade", "ExchangeCode", true);
@@ -96,7 +96,7 @@ public void buildScheme(SchemeBuilder builder) {
}
for (char exchange : SystemProperties.getProperty("com.dxfeed.event.market.impl.Trade.exchanges", "ABCDEFGHIJKLMNOPQRSTUVWXYZ").toCharArray()) {
String recordName = "Trade&" + exchange;
- builder.addOptionalField(recordName, "Last.Time", SerialFieldType.TIME, "Trade", "Time", true);
+ builder.addOptionalField(recordName, "Last.Time", SerialFieldType.TIME_SECONDS, "Trade", "Time", true);
builder.addOptionalField(recordName, "Last.Sequence", SerialFieldType.SEQUENCE, "Trade", "Sequence", true);
builder.addOptionalField(recordName, "Last.TimeNanoPart", SerialFieldType.COMPACT_INT, "Trade", "TimeNanoPart", false);
builder.addRequiredField(recordName, "Last.Price", select(SerialFieldType.DECIMAL, "dxscheme.price"));
@@ -108,7 +108,7 @@ public void buildScheme(SchemeBuilder builder) {
builder.addOptionalField(recordName, "Last.Flags", SerialFieldType.COMPACT_INT, "Trade", "Flags", true);
}
- builder.addOptionalField("TradeETH", "ETHLast.Time", SerialFieldType.TIME, "TradeETH", "Time", true);
+ builder.addOptionalField("TradeETH", "ETHLast.Time", SerialFieldType.TIME_SECONDS, "TradeETH", "Time", true);
builder.addOptionalField("TradeETH", "ETHLast.Sequence", SerialFieldType.SEQUENCE, "TradeETH", "Sequence", true);
builder.addOptionalField("TradeETH", "Last.TimeNanoPart", SerialFieldType.COMPACT_INT, "TradeETH", "TimeNanoPart", false);
builder.addOptionalField("TradeETH", "ETHLast.Exchange", SerialFieldType.UTF_CHAR, "TradeETH", "ExchangeCode", true);
@@ -120,7 +120,7 @@ public void buildScheme(SchemeBuilder builder) {
builder.addRequiredField("TradeETH", "ETHLast.Flags", SerialFieldType.COMPACT_INT);
for (char exchange : SystemProperties.getProperty("com.dxfeed.event.market.impl.TradeETH.exchanges", "ABCDEFGHIJKLMNOPQRSTUVWXYZ").toCharArray()) {
String recordName = "TradeETH&" + exchange;
- builder.addOptionalField(recordName, "ETHLast.Time", SerialFieldType.TIME, "TradeETH", "Time", true);
+ builder.addOptionalField(recordName, "ETHLast.Time", SerialFieldType.TIME_SECONDS, "TradeETH", "Time", true);
builder.addOptionalField(recordName, "ETHLast.Sequence", SerialFieldType.SEQUENCE, "TradeETH", "Sequence", true);
builder.addOptionalField(recordName, "Last.TimeNanoPart", SerialFieldType.COMPACT_INT, "TradeETH", "TimeNanoPart", false);
builder.addRequiredField(recordName, "ETHLast.Price", select(SerialFieldType.DECIMAL, "dxscheme.price"));
@@ -171,7 +171,7 @@ public void buildScheme(SchemeBuilder builder) {
String recordName = "Book&" + exchange;
builder.addRequiredField(recordName, "ID", SerialFieldType.COMPACT_INT, SchemeFieldTime.FIRST_TIME_INT_FIELD);
builder.addRequiredField(recordName, "Sequence", SerialFieldType.VOID, SchemeFieldTime.SECOND_TIME_INT_FIELD);
- builder.addRequiredField(recordName, "Time", SerialFieldType.TIME);
+ builder.addRequiredField(recordName, "Time", SerialFieldType.TIME_SECONDS);
builder.addRequiredField(recordName, "Type", SerialFieldType.UTF_CHAR);
builder.addRequiredField(recordName, "Price", select(SerialFieldType.DECIMAL, "dxscheme.price"));
builder.addRequiredField(recordName, "Size", select(SerialFieldType.COMPACT_INT, "dxscheme.size"));
@@ -190,25 +190,41 @@ public void buildScheme(SchemeBuilder builder) {
builder.addOptionalField("Profile", "FreeFloat", select(SerialFieldType.DECIMAL), "Profile", "FreeFloat", true);
builder.addOptionalField("Profile", "HighLimitPrice", select(SerialFieldType.DECIMAL, "dxscheme.price"), "Profile", "HighLimitPrice", true);
builder.addOptionalField("Profile", "LowLimitPrice", select(SerialFieldType.DECIMAL, "dxscheme.price"), "Profile", "LowLimitPrice", true);
- builder.addOptionalField("Profile", "Halt.StartTime", SerialFieldType.TIME, "Profile", "HaltStartTime", true);
- builder.addOptionalField("Profile", "Halt.EndTime", SerialFieldType.TIME, "Profile", "HaltEndTime", true);
+ builder.addOptionalField("Profile", "Halt.StartTime", SerialFieldType.TIME_SECONDS, "Profile", "HaltStartTime", true);
+ builder.addOptionalField("Profile", "Halt.EndTime", SerialFieldType.TIME_SECONDS, "Profile", "HaltEndTime", true);
builder.addOptionalField("Profile", "Flags", SerialFieldType.COMPACT_INT, "Profile", "Flags", true);
builder.addRequiredField("Profile", "Description", SerialFieldType.UTF_CHAR_ARRAY);
builder.addOptionalField("Profile", "StatusReason", SerialFieldType.UTF_CHAR_ARRAY, "Profile", "StatusReason", true);
- for (String suffix : SystemProperties.getProperty("com.dxfeed.event.market.impl.Order.suffixes", "|#NTV|#ntv|#NFX|#ESPD|#XNFI|#ICE|#ISE|#DEA|#DEX|#BYX|#BZX|#BATE|#CHIX|#CEUX|#BXTR|#IST|#BI20|#ABE|#FAIR|#GLBX|#glbx|#ERIS|#XEUR|#xeur|#CFE|#SMFE").split("\\|")) {
+ for (String suffix : SystemProperties.getProperty("com.dxfeed.event.market.impl.Order.suffixes", "|#NTV|#ntv|#NFX|#ESPD|#XNFI|#ICE|#ISE|#DEA|#DEX|#BYX|#BZX|#BATE|#CHIX|#CEUX|#BXTR|#IST|#BI20|#ABE|#FAIR|#GLBX|#glbx|#ERIS|#XEUR|#xeur|#CFE|#C2OX|#SMFE").split("\\|")) {
String recordName = "Order" + suffix;
builder.addRequiredField(recordName, "Void", SerialFieldType.VOID, SchemeFieldTime.FIRST_TIME_INT_FIELD);
builder.addRequiredField(recordName, "Index", SerialFieldType.COMPACT_INT, SchemeFieldTime.SECOND_TIME_INT_FIELD);
- builder.addRequiredField(recordName, "Time", SerialFieldType.TIME);
+ builder.addRequiredField(recordName, "Time", SerialFieldType.TIME_SECONDS);
builder.addRequiredField(recordName, "Sequence", SerialFieldType.SEQUENCE);
builder.addOptionalField(recordName, "TimeNanoPart", SerialFieldType.COMPACT_INT, "Order", "TimeNanoPart", false);
+ if (SystemProperties.getBooleanProperty("dxscheme.fob", false)) {
+ if (suffix.matches(SystemProperties.getProperty("com.dxfeed.event.market.impl.Order.fob.suffixes", "|#NTV")))
+ builder.addOptionalField(recordName, "ActionTime", SerialFieldType.TIME_MILLIS, "Order", "ActionTime", true);
+ if (suffix.matches(SystemProperties.getProperty("com.dxfeed.event.market.impl.Order.fob.suffixes", "|#NTV")))
+ builder.addOptionalField(recordName, "OrderId", SerialFieldType.LONG, "Order", "OrderId", true);
+ if (suffix.matches(SystemProperties.getProperty("com.dxfeed.event.market.impl.Order.fob.suffixes", "|#NTV")))
+ builder.addOptionalField(recordName, "AuxOrderId", SerialFieldType.LONG, "Order", "AuxOrderId", true);
+ }
builder.addRequiredField(recordName, "Price", select(SerialFieldType.DECIMAL, "dxscheme.price"));
builder.addRequiredField(recordName, "Size", select(SerialFieldType.COMPACT_INT, "dxscheme.size"));
builder.addOptionalField(recordName, "ExecutedSize", select(SerialFieldType.DECIMAL), "Order", "ExecutedSize", false);
if (suffix.matches(SystemProperties.getProperty("com.dxfeed.event.order.impl.Order.suffixes.count", "")))
builder.addOptionalField(recordName, "Count", select(SerialFieldType.COMPACT_INT), "Order", "Count", true);
builder.addRequiredField(recordName, "Flags", SerialFieldType.COMPACT_INT);
+ if (SystemProperties.getBooleanProperty("dxscheme.fob", false)) {
+ if (suffix.matches(SystemProperties.getProperty("com.dxfeed.event.market.impl.Order.fob.suffixes", "|#NTV")))
+ builder.addOptionalField(recordName, "TradeId", SerialFieldType.LONG, "Order", "TradeId", true);
+ if (suffix.matches(SystemProperties.getProperty("com.dxfeed.event.market.impl.Order.fob.suffixes", "|#NTV")))
+ builder.addOptionalField(recordName, "TradePrice", select(SerialFieldType.DECIMAL), "Order", "TradePrice", true);
+ if (suffix.matches(SystemProperties.getProperty("com.dxfeed.event.market.impl.Order.fob.suffixes", "|#NTV")))
+ builder.addOptionalField(recordName, "TradeSize", select(SerialFieldType.DECIMAL), "Order", "TradeSize", true);
+ }
if (suffix.matches(SystemProperties.getProperty("com.dxfeed.event.order.impl.Order.suffixes.mmid", "|#NTV|#BATE|#CHIX|#CEUX|#BXTR")))
builder.addOptionalField(recordName, "MMID", SerialFieldType.SHORT_STRING, "Order", "MarketMaker", true);
builder.addOptionalField(recordName, "IcebergPeakSize", select(SerialFieldType.DECIMAL), "Order", "IcebergPeakSize", false);
@@ -221,15 +237,31 @@ public void buildScheme(SchemeBuilder builder) {
String recordName = "AnalyticOrder" + suffix;
builder.addRequiredField(recordName, "Void", SerialFieldType.VOID, SchemeFieldTime.FIRST_TIME_INT_FIELD);
builder.addRequiredField(recordName, "Index", SerialFieldType.COMPACT_INT, SchemeFieldTime.SECOND_TIME_INT_FIELD);
- builder.addRequiredField(recordName, "Time", SerialFieldType.TIME);
+ builder.addRequiredField(recordName, "Time", SerialFieldType.TIME_SECONDS);
builder.addRequiredField(recordName, "Sequence", SerialFieldType.SEQUENCE);
builder.addOptionalField(recordName, "TimeNanoPart", SerialFieldType.COMPACT_INT, "AnalyticOrder", "TimeNanoPart", false);
+ if (SystemProperties.getBooleanProperty("dxscheme.fob", false)) {
+ if (suffix.matches(SystemProperties.getProperty("com.dxfeed.event.market.impl.Order.fob.suffixes", "|#NTV")))
+ builder.addOptionalField(recordName, "ActionTime", SerialFieldType.TIME_MILLIS, "AnalyticOrder", "ActionTime", true);
+ if (suffix.matches(SystemProperties.getProperty("com.dxfeed.event.market.impl.Order.fob.suffixes", "|#NTV")))
+ builder.addOptionalField(recordName, "OrderId", SerialFieldType.LONG, "AnalyticOrder", "OrderId", true);
+ if (suffix.matches(SystemProperties.getProperty("com.dxfeed.event.market.impl.Order.fob.suffixes", "|#NTV")))
+ builder.addOptionalField(recordName, "AuxOrderId", SerialFieldType.LONG, "AnalyticOrder", "AuxOrderId", true);
+ }
builder.addRequiredField(recordName, "Price", select(SerialFieldType.DECIMAL, "dxscheme.price"));
builder.addRequiredField(recordName, "Size", select(SerialFieldType.COMPACT_INT, "dxscheme.size"));
builder.addOptionalField(recordName, "ExecutedSize", select(SerialFieldType.DECIMAL), "AnalyticOrder", "ExecutedSize", false);
if (suffix.matches(SystemProperties.getProperty("com.dxfeed.event.order.impl.AnalyticOrder.suffixes.count", "")))
builder.addOptionalField(recordName, "Count", select(SerialFieldType.COMPACT_INT), "AnalyticOrder", "Count", true);
builder.addRequiredField(recordName, "Flags", SerialFieldType.COMPACT_INT);
+ if (SystemProperties.getBooleanProperty("dxscheme.fob", false)) {
+ if (suffix.matches(SystemProperties.getProperty("com.dxfeed.event.market.impl.Order.fob.suffixes", "|#NTV")))
+ builder.addOptionalField(recordName, "TradeId", SerialFieldType.LONG, "AnalyticOrder", "TradeId", true);
+ if (suffix.matches(SystemProperties.getProperty("com.dxfeed.event.market.impl.Order.fob.suffixes", "|#NTV")))
+ builder.addOptionalField(recordName, "TradePrice", select(SerialFieldType.DECIMAL), "AnalyticOrder", "TradePrice", true);
+ if (suffix.matches(SystemProperties.getProperty("com.dxfeed.event.market.impl.Order.fob.suffixes", "|#NTV")))
+ builder.addOptionalField(recordName, "TradeSize", select(SerialFieldType.DECIMAL), "AnalyticOrder", "TradeSize", true);
+ }
if (suffix.matches(SystemProperties.getProperty("com.dxfeed.event.order.impl.AnalyticOrder.suffixes.mmid", "|#NTV|#BATE|#CHIX|#CEUX|#BXTR")))
builder.addOptionalField(recordName, "MMID", SerialFieldType.SHORT_STRING, "AnalyticOrder", "MarketMaker", true);
builder.addOptionalField(recordName, "IcebergPeakSize", select(SerialFieldType.DECIMAL), "AnalyticOrder", "IcebergPeakSize", false);
@@ -242,30 +274,46 @@ public void buildScheme(SchemeBuilder builder) {
String recordName = "SpreadOrder" + suffix;
builder.addRequiredField(recordName, "Void", SerialFieldType.VOID, SchemeFieldTime.FIRST_TIME_INT_FIELD);
builder.addRequiredField(recordName, "Index", SerialFieldType.COMPACT_INT, SchemeFieldTime.SECOND_TIME_INT_FIELD);
- builder.addRequiredField(recordName, "Time", SerialFieldType.TIME);
+ builder.addRequiredField(recordName, "Time", SerialFieldType.TIME_SECONDS);
builder.addRequiredField(recordName, "Sequence", SerialFieldType.SEQUENCE);
builder.addOptionalField(recordName, "TimeNanoPart", SerialFieldType.COMPACT_INT, "SpreadOrder", "TimeNanoPart", false);
+ if (SystemProperties.getBooleanProperty("dxscheme.fob", false)) {
+ if (suffix.matches(SystemProperties.getProperty("com.dxfeed.event.market.impl.Order.fob.suffixes", "|#NTV")))
+ builder.addOptionalField(recordName, "ActionTime", SerialFieldType.TIME_MILLIS, "SpreadOrder", "ActionTime", true);
+ if (suffix.matches(SystemProperties.getProperty("com.dxfeed.event.market.impl.Order.fob.suffixes", "|#NTV")))
+ builder.addOptionalField(recordName, "OrderId", SerialFieldType.LONG, "SpreadOrder", "OrderId", true);
+ if (suffix.matches(SystemProperties.getProperty("com.dxfeed.event.market.impl.Order.fob.suffixes", "|#NTV")))
+ builder.addOptionalField(recordName, "AuxOrderId", SerialFieldType.LONG, "SpreadOrder", "AuxOrderId", true);
+ }
builder.addRequiredField(recordName, "Price", select(SerialFieldType.DECIMAL, "dxscheme.price"));
builder.addRequiredField(recordName, "Size", select(SerialFieldType.COMPACT_INT, "dxscheme.size"));
builder.addOptionalField(recordName, "ExecutedSize", select(SerialFieldType.DECIMAL), "SpreadOrder", "ExecutedSize", false);
if (suffix.matches(SystemProperties.getProperty("com.dxfeed.event.order.impl.SpreadOrder.suffixes.count", "")))
builder.addOptionalField(recordName, "Count", select(SerialFieldType.COMPACT_INT), "SpreadOrder", "Count", true);
builder.addRequiredField(recordName, "Flags", SerialFieldType.COMPACT_INT);
+ if (SystemProperties.getBooleanProperty("dxscheme.fob", false)) {
+ if (suffix.matches(SystemProperties.getProperty("com.dxfeed.event.market.impl.Order.fob.suffixes", "|#NTV")))
+ builder.addOptionalField(recordName, "TradeId", SerialFieldType.LONG, "SpreadOrder", "TradeId", true);
+ if (suffix.matches(SystemProperties.getProperty("com.dxfeed.event.market.impl.Order.fob.suffixes", "|#NTV")))
+ builder.addOptionalField(recordName, "TradePrice", select(SerialFieldType.DECIMAL), "SpreadOrder", "TradePrice", true);
+ if (suffix.matches(SystemProperties.getProperty("com.dxfeed.event.market.impl.Order.fob.suffixes", "|#NTV")))
+ builder.addOptionalField(recordName, "TradeSize", select(SerialFieldType.DECIMAL), "SpreadOrder", "TradeSize", true);
+ }
builder.addRequiredField(recordName, "SpreadSymbol", SerialFieldType.UTF_CHAR_ARRAY);
}
builder.addRequiredField("MarketMaker", "MMExchange", SerialFieldType.UTF_CHAR, SchemeFieldTime.FIRST_TIME_INT_FIELD);
builder.addRequiredField("MarketMaker", "MMID", SerialFieldType.SHORT_STRING, SchemeFieldTime.SECOND_TIME_INT_FIELD);
- builder.addOptionalField("MarketMaker", "MMBid.Time", SerialFieldType.TIME, "Order", "BidTime", true);
+ builder.addOptionalField("MarketMaker", "MMBid.Time", SerialFieldType.TIME_SECONDS, "Order", "BidTime", true);
builder.addRequiredField("MarketMaker", "MMBid.Price", select(SerialFieldType.DECIMAL, "dxscheme.price"));
builder.addRequiredField("MarketMaker", "MMBid.Size", select(SerialFieldType.COMPACT_INT, "dxscheme.size"));
builder.addOptionalField("MarketMaker", "MMBid.Count", select(SerialFieldType.COMPACT_INT), "Order", "BidCount", true);
- builder.addOptionalField("MarketMaker", "MMAsk.Time", SerialFieldType.TIME, "Order", "AskTime", true);
+ builder.addOptionalField("MarketMaker", "MMAsk.Time", SerialFieldType.TIME_SECONDS, "Order", "AskTime", true);
builder.addRequiredField("MarketMaker", "MMAsk.Price", select(SerialFieldType.DECIMAL, "dxscheme.price"));
builder.addRequiredField("MarketMaker", "MMAsk.Size", select(SerialFieldType.COMPACT_INT, "dxscheme.size"));
builder.addOptionalField("MarketMaker", "MMAsk.Count", select(SerialFieldType.COMPACT_INT), "Order", "AskCount", true);
- builder.addRequiredField("TimeAndSale", "Time", SerialFieldType.TIME, SchemeFieldTime.FIRST_TIME_INT_FIELD);
+ builder.addRequiredField("TimeAndSale", "Time", SerialFieldType.TIME_SECONDS, SchemeFieldTime.FIRST_TIME_INT_FIELD);
builder.addRequiredField("TimeAndSale", "Sequence", SerialFieldType.SEQUENCE, SchemeFieldTime.SECOND_TIME_INT_FIELD);
builder.addOptionalField("TimeAndSale", "TimeNanoPart", SerialFieldType.COMPACT_INT, "TimeAndSale", "TimeNanoPart", false);
builder.addRequiredField("TimeAndSale", "Exchange", SerialFieldType.UTF_CHAR);
@@ -279,7 +327,7 @@ public void buildScheme(SchemeBuilder builder) {
builder.addOptionalField("TimeAndSale", "Seller", SerialFieldType.UTF_CHAR_ARRAY, "TimeAndSale", "Seller", false);
for (char exchange : SystemProperties.getProperty("com.dxfeed.event.market.impl.TimeAndSale.exchanges", "ABCDEFGHIJKLMNOPQRSTUVWXYZ").toCharArray()) {
String recordName = "TimeAndSale&" + exchange;
- builder.addRequiredField(recordName, "Time", SerialFieldType.TIME, SchemeFieldTime.FIRST_TIME_INT_FIELD);
+ builder.addRequiredField(recordName, "Time", SerialFieldType.TIME_SECONDS, SchemeFieldTime.FIRST_TIME_INT_FIELD);
builder.addRequiredField(recordName, "Sequence", SerialFieldType.SEQUENCE, SchemeFieldTime.SECOND_TIME_INT_FIELD);
builder.addOptionalField(recordName, "TimeNanoPart", SerialFieldType.COMPACT_INT, "TimeAndSale", "TimeNanoPart", false);
builder.addRequiredField(recordName, "Exchange", SerialFieldType.UTF_CHAR);
diff --git a/dxfeed-impl/src/main/java/com/dxfeed/event/market/OrderDelegate.java b/dxfeed-impl/src/main/java/com/dxfeed/event/market/OrderDelegate.java
index 76f5f9257..ccca49dcb 100644
--- a/dxfeed-impl/src/main/java/com/dxfeed/event/market/OrderDelegate.java
+++ b/dxfeed-impl/src/main/java/com/dxfeed/event/market/OrderDelegate.java
@@ -47,11 +47,17 @@ public Order getEvent(Order event, RecordCursor cursor) {
event.setIndex(((long) getSource().id() << 32) | (m.getIndex(cursor) & 0xFFFFFFFFL));
event.setTimeSequence((((long) m.getTimeSeconds(cursor)) << 32) | (m.getSequence(cursor) & 0xFFFFFFFFL));
event.setTimeNanoPart(m.getTimeNanoPart(cursor));
+ event.setActionTime(m.getActionTimeMillis(cursor));
+ event.setOrderId(m.getOrderId(cursor));
+ event.setAuxOrderId(m.getAuxOrderId(cursor));
event.setPrice(m.getPrice(cursor));
event.setSizeAsDouble(m.getSizeDouble(cursor));
event.setExecutedSize(m.getExecutedSize(cursor));
event.setCount(m.getCount(cursor));
event.setFlags(m.getFlags(cursor));
+ event.setTradeId(m.getTradeId(cursor));
+ event.setTradePrice(m.getTradePrice(cursor));
+ event.setTradeSize(m.getTradeSize(cursor));
event.setMarketMaker(m.getMarketMakerString(cursor));
return event;
}
@@ -65,11 +71,17 @@ public RecordCursor putEvent(Order event, RecordBuffer buf) {
m.setTimeSeconds(cursor, (int) (event.getTimeSequence() >>> 32));
m.setSequence(cursor, (int) event.getTimeSequence());
m.setTimeNanoPart(cursor, event.getTimeNanoPart());
+ m.setActionTimeMillis(cursor, event.getActionTime());
+ m.setOrderId(cursor, event.getOrderId());
+ m.setAuxOrderId(cursor, event.getAuxOrderId());
m.setPrice(cursor, event.getPrice());
m.setSizeDouble(cursor, event.getSizeAsDouble());
m.setExecutedSize(cursor, event.getExecutedSize());
m.setCount(cursor, (int) event.getCount());
m.setFlags(cursor, event.getFlags());
+ m.setTradeId(cursor, event.getTradeId());
+ m.setTradePrice(cursor, event.getTradePrice());
+ m.setTradeSize(cursor, event.getTradeSize());
m.setMarketMakerString(cursor, event.getMarketMaker());
if (index < 0)
throw new IllegalArgumentException("Invalid index to publish");
diff --git a/dxfeed-impl/src/main/java/com/dxfeed/event/market/SpreadOrderDelegate.java b/dxfeed-impl/src/main/java/com/dxfeed/event/market/SpreadOrderDelegate.java
index 7bb8768bf..58a6b9b32 100644
--- a/dxfeed-impl/src/main/java/com/dxfeed/event/market/SpreadOrderDelegate.java
+++ b/dxfeed-impl/src/main/java/com/dxfeed/event/market/SpreadOrderDelegate.java
@@ -47,11 +47,17 @@ public SpreadOrder getEvent(SpreadOrder event, RecordCursor cursor) {
event.setIndex(((long) getSource().id() << 32) | (m.getIndex(cursor) & 0xFFFFFFFFL));
event.setTimeSequence((((long) m.getTimeSeconds(cursor)) << 32) | (m.getSequence(cursor) & 0xFFFFFFFFL));
event.setTimeNanoPart(m.getTimeNanoPart(cursor));
+ event.setActionTime(m.getActionTimeMillis(cursor));
+ event.setOrderId(m.getOrderId(cursor));
+ event.setAuxOrderId(m.getAuxOrderId(cursor));
event.setPrice(m.getPrice(cursor));
event.setSizeAsDouble(m.getSizeDouble(cursor));
event.setExecutedSize(m.getExecutedSize(cursor));
event.setCount(m.getCount(cursor));
event.setFlags(m.getFlags(cursor));
+ event.setTradeId(m.getTradeId(cursor));
+ event.setTradePrice(m.getTradePrice(cursor));
+ event.setTradeSize(m.getTradeSize(cursor));
event.setSpreadSymbol(m.getSpreadSymbol(cursor));
return event;
}
@@ -65,11 +71,17 @@ public RecordCursor putEvent(SpreadOrder event, RecordBuffer buf) {
m.setTimeSeconds(cursor, (int) (event.getTimeSequence() >>> 32));
m.setSequence(cursor, (int) event.getTimeSequence());
m.setTimeNanoPart(cursor, event.getTimeNanoPart());
+ m.setActionTimeMillis(cursor, event.getActionTime());
+ m.setOrderId(cursor, event.getOrderId());
+ m.setAuxOrderId(cursor, event.getAuxOrderId());
m.setPrice(cursor, event.getPrice());
m.setSizeDouble(cursor, event.getSizeAsDouble());
m.setExecutedSize(cursor, event.getExecutedSize());
m.setCount(cursor, (int) event.getCount());
m.setFlags(cursor, event.getFlags());
+ m.setTradeId(cursor, event.getTradeId());
+ m.setTradePrice(cursor, event.getTradePrice());
+ m.setTradeSize(cursor, event.getTradeSize());
m.setSpreadSymbol(cursor, event.getSpreadSymbol());
if (index < 0)
throw new IllegalArgumentException("Invalid index to publish");
diff --git a/dxfeed-impl/src/main/java/com/dxfeed/event/market/impl/AnalyticOrderMapping.java b/dxfeed-impl/src/main/java/com/dxfeed/event/market/impl/AnalyticOrderMapping.java
index 4ebeebb6a..665f245dd 100644
--- a/dxfeed-impl/src/main/java/com/dxfeed/event/market/impl/AnalyticOrderMapping.java
+++ b/dxfeed-impl/src/main/java/com/dxfeed/event/market/impl/AnalyticOrderMapping.java
@@ -23,11 +23,17 @@ public class AnalyticOrderMapping extends OrderBaseMapping {
private final int iTime;
private final int iSequence;
private final int iTimeNanoPart;
+ private final int iActionTime;
+ private final int iOrderId;
+ private final int iAuxOrderId;
private final int iPrice;
private final int iSize;
private final int iExecutedSize;
private final int iCount;
private final int iFlags;
+ private final int iTradeId;
+ private final int iTradePrice;
+ private final int iTradeSize;
private final int iMarketMaker;
private final int iIcebergPeakSize;
private final int iIcebergHiddenSize;
@@ -40,11 +46,17 @@ public AnalyticOrderMapping(DataRecord record) {
iTime = MappingUtil.findIntField(record, "Time", true);
iSequence = MappingUtil.findIntField(record, "Sequence", true);
iTimeNanoPart = MappingUtil.findIntField(record, "TimeNanoPart", false);
+ iActionTime = MappingUtil.findIntField(record, "ActionTime", false);
+ iOrderId = MappingUtil.findIntField(record, "OrderId", false);
+ iAuxOrderId = MappingUtil.findIntField(record, "AuxOrderId", false);
iPrice = findIntField("Price", true);
iSize = findIntField("Size", true);
iExecutedSize = findIntField("ExecutedSize", false);
iCount = findIntField("Count", false);
iFlags = MappingUtil.findIntField(record, "Flags", true);
+ iTradeId = MappingUtil.findIntField(record, "TradeId", false);
+ iTradePrice = findIntField("TradePrice", false);
+ iTradeSize = findIntField("TradeSize", false);
iMarketMaker = MappingUtil.findIntField(record, "MMID", false);
iIcebergPeakSize = findIntField("IcebergPeakSize", false);
iIcebergHiddenSize = findIntField("IcebergHiddenSize", false);
@@ -97,6 +109,54 @@ public void setTimeNanoPart(RecordCursor cursor, int timeNanoPart) {
setInt(cursor, iTimeNanoPart, timeNanoPart);
}
+ public long getActionTimeMillis(RecordCursor cursor) {
+ if (iActionTime < 0)
+ return 0;
+ return getLong(cursor, iActionTime);
+ }
+
+ public void setActionTimeMillis(RecordCursor cursor, long actionTime) {
+ if (iActionTime < 0)
+ return;
+ setLong(cursor, iActionTime, actionTime);
+ }
+
+ public int getActionTimeSeconds(RecordCursor cursor) {
+ if (iActionTime < 0)
+ return 0;
+ return TimeUtil.getSecondsFromTime(getLong(cursor, iActionTime));
+ }
+
+ public void setActionTimeSeconds(RecordCursor cursor, int actionTime) {
+ if (iActionTime < 0)
+ return;
+ setLong(cursor, iActionTime, actionTime * 1000L);
+ }
+
+ public long getOrderId(RecordCursor cursor) {
+ if (iOrderId < 0)
+ return 0;
+ return getLong(cursor, iOrderId);
+ }
+
+ public void setOrderId(RecordCursor cursor, long orderId) {
+ if (iOrderId < 0)
+ return;
+ setLong(cursor, iOrderId, orderId);
+ }
+
+ public long getAuxOrderId(RecordCursor cursor) {
+ if (iAuxOrderId < 0)
+ return 0;
+ return getLong(cursor, iAuxOrderId);
+ }
+
+ public void setAuxOrderId(RecordCursor cursor, long auxOrderId) {
+ if (iAuxOrderId < 0)
+ return;
+ setLong(cursor, iAuxOrderId, auxOrderId);
+ }
+
public double getPrice(RecordCursor cursor) {
return getAsDouble(cursor, iPrice);
}
@@ -265,6 +325,90 @@ public void setFlags(RecordCursor cursor, int flags) {
setInt(cursor, iFlags, flags);
}
+ public long getTradeId(RecordCursor cursor) {
+ if (iTradeId < 0)
+ return 0;
+ return getLong(cursor, iTradeId);
+ }
+
+ public void setTradeId(RecordCursor cursor, long tradeId) {
+ if (iTradeId < 0)
+ return;
+ setLong(cursor, iTradeId, tradeId);
+ }
+
+ public double getTradePrice(RecordCursor cursor) {
+ if (iTradePrice < 0)
+ return Double.NaN;
+ return getAsDouble(cursor, iTradePrice);
+ }
+
+ public void setTradePrice(RecordCursor cursor, double tradePrice) {
+ if (iTradePrice < 0)
+ return;
+ setAsDouble(cursor, iTradePrice, tradePrice);
+ }
+
+ public int getTradePriceDecimal(RecordCursor cursor) {
+ if (iTradePrice < 0)
+ return 0;
+ return getAsTinyDecimal(cursor, iTradePrice);
+ }
+
+ public void setTradePriceDecimal(RecordCursor cursor, int tradePrice) {
+ if (iTradePrice < 0)
+ return;
+ setAsTinyDecimal(cursor, iTradePrice, tradePrice);
+ }
+
+ public long getTradePriceWideDecimal(RecordCursor cursor) {
+ if (iTradePrice < 0)
+ return 0;
+ return getAsWideDecimal(cursor, iTradePrice);
+ }
+
+ public void setTradePriceWideDecimal(RecordCursor cursor, long tradePrice) {
+ if (iTradePrice < 0)
+ return;
+ setAsWideDecimal(cursor, iTradePrice, tradePrice);
+ }
+
+ public double getTradeSize(RecordCursor cursor) {
+ if (iTradeSize < 0)
+ return Double.NaN;
+ return getAsDouble(cursor, iTradeSize);
+ }
+
+ public void setTradeSize(RecordCursor cursor, double tradeSize) {
+ if (iTradeSize < 0)
+ return;
+ setAsDouble(cursor, iTradeSize, tradeSize);
+ }
+
+ public int getTradeSizeDecimal(RecordCursor cursor) {
+ if (iTradeSize < 0)
+ return 0;
+ return getAsTinyDecimal(cursor, iTradeSize);
+ }
+
+ public void setTradeSizeDecimal(RecordCursor cursor, int tradeSize) {
+ if (iTradeSize < 0)
+ return;
+ setAsTinyDecimal(cursor, iTradeSize, tradeSize);
+ }
+
+ public long getTradeSizeWideDecimal(RecordCursor cursor) {
+ if (iTradeSize < 0)
+ return 0;
+ return getAsWideDecimal(cursor, iTradeSize);
+ }
+
+ public void setTradeSizeWideDecimal(RecordCursor cursor, long tradeSize) {
+ if (iTradeSize < 0)
+ return;
+ setAsWideDecimal(cursor, iTradeSize, tradeSize);
+ }
+
@Deprecated
public String getMMIDString(RecordCursor cursor) {
if (iMarketMaker < 0)
diff --git a/dxfeed-impl/src/main/java/com/dxfeed/event/market/impl/OrderMapping.java b/dxfeed-impl/src/main/java/com/dxfeed/event/market/impl/OrderMapping.java
index 570563f16..6f32178e5 100644
--- a/dxfeed-impl/src/main/java/com/dxfeed/event/market/impl/OrderMapping.java
+++ b/dxfeed-impl/src/main/java/com/dxfeed/event/market/impl/OrderMapping.java
@@ -23,11 +23,17 @@ public class OrderMapping extends OrderBaseMapping {
private final int iTime;
private final int iSequence;
private final int iTimeNanoPart;
+ private final int iActionTime;
+ private final int iOrderId;
+ private final int iAuxOrderId;
private final int iPrice;
private final int iSize;
private final int iExecutedSize;
private final int iCount;
private final int iFlags;
+ private final int iTradeId;
+ private final int iTradePrice;
+ private final int iTradeSize;
private final int iMarketMaker;
private final int iIcebergPeakSize;
private final int iIcebergHiddenSize;
@@ -40,11 +46,17 @@ public OrderMapping(DataRecord record) {
iTime = MappingUtil.findIntField(record, "Time", true);
iSequence = MappingUtil.findIntField(record, "Sequence", true);
iTimeNanoPart = MappingUtil.findIntField(record, "TimeNanoPart", false);
+ iActionTime = MappingUtil.findIntField(record, "ActionTime", false);
+ iOrderId = MappingUtil.findIntField(record, "OrderId", false);
+ iAuxOrderId = MappingUtil.findIntField(record, "AuxOrderId", false);
iPrice = findIntField("Price", true);
iSize = findIntField("Size", true);
iExecutedSize = findIntField("ExecutedSize", false);
iCount = findIntField("Count", false);
iFlags = MappingUtil.findIntField(record, "Flags", true);
+ iTradeId = MappingUtil.findIntField(record, "TradeId", false);
+ iTradePrice = findIntField("TradePrice", false);
+ iTradeSize = findIntField("TradeSize", false);
iMarketMaker = MappingUtil.findIntField(record, "MMID", false);
iIcebergPeakSize = findIntField("IcebergPeakSize", false);
iIcebergHiddenSize = findIntField("IcebergHiddenSize", false);
@@ -97,6 +109,54 @@ public void setTimeNanoPart(RecordCursor cursor, int timeNanoPart) {
setInt(cursor, iTimeNanoPart, timeNanoPart);
}
+ public long getActionTimeMillis(RecordCursor cursor) {
+ if (iActionTime < 0)
+ return 0;
+ return getLong(cursor, iActionTime);
+ }
+
+ public void setActionTimeMillis(RecordCursor cursor, long actionTime) {
+ if (iActionTime < 0)
+ return;
+ setLong(cursor, iActionTime, actionTime);
+ }
+
+ public int getActionTimeSeconds(RecordCursor cursor) {
+ if (iActionTime < 0)
+ return 0;
+ return TimeUtil.getSecondsFromTime(getLong(cursor, iActionTime));
+ }
+
+ public void setActionTimeSeconds(RecordCursor cursor, int actionTime) {
+ if (iActionTime < 0)
+ return;
+ setLong(cursor, iActionTime, actionTime * 1000L);
+ }
+
+ public long getOrderId(RecordCursor cursor) {
+ if (iOrderId < 0)
+ return 0;
+ return getLong(cursor, iOrderId);
+ }
+
+ public void setOrderId(RecordCursor cursor, long orderId) {
+ if (iOrderId < 0)
+ return;
+ setLong(cursor, iOrderId, orderId);
+ }
+
+ public long getAuxOrderId(RecordCursor cursor) {
+ if (iAuxOrderId < 0)
+ return 0;
+ return getLong(cursor, iAuxOrderId);
+ }
+
+ public void setAuxOrderId(RecordCursor cursor, long auxOrderId) {
+ if (iAuxOrderId < 0)
+ return;
+ setLong(cursor, iAuxOrderId, auxOrderId);
+ }
+
public double getPrice(RecordCursor cursor) {
return getAsDouble(cursor, iPrice);
}
@@ -265,6 +325,90 @@ public void setFlags(RecordCursor cursor, int flags) {
setInt(cursor, iFlags, flags);
}
+ public long getTradeId(RecordCursor cursor) {
+ if (iTradeId < 0)
+ return 0;
+ return getLong(cursor, iTradeId);
+ }
+
+ public void setTradeId(RecordCursor cursor, long tradeId) {
+ if (iTradeId < 0)
+ return;
+ setLong(cursor, iTradeId, tradeId);
+ }
+
+ public double getTradePrice(RecordCursor cursor) {
+ if (iTradePrice < 0)
+ return Double.NaN;
+ return getAsDouble(cursor, iTradePrice);
+ }
+
+ public void setTradePrice(RecordCursor cursor, double tradePrice) {
+ if (iTradePrice < 0)
+ return;
+ setAsDouble(cursor, iTradePrice, tradePrice);
+ }
+
+ public int getTradePriceDecimal(RecordCursor cursor) {
+ if (iTradePrice < 0)
+ return 0;
+ return getAsTinyDecimal(cursor, iTradePrice);
+ }
+
+ public void setTradePriceDecimal(RecordCursor cursor, int tradePrice) {
+ if (iTradePrice < 0)
+ return;
+ setAsTinyDecimal(cursor, iTradePrice, tradePrice);
+ }
+
+ public long getTradePriceWideDecimal(RecordCursor cursor) {
+ if (iTradePrice < 0)
+ return 0;
+ return getAsWideDecimal(cursor, iTradePrice);
+ }
+
+ public void setTradePriceWideDecimal(RecordCursor cursor, long tradePrice) {
+ if (iTradePrice < 0)
+ return;
+ setAsWideDecimal(cursor, iTradePrice, tradePrice);
+ }
+
+ public double getTradeSize(RecordCursor cursor) {
+ if (iTradeSize < 0)
+ return Double.NaN;
+ return getAsDouble(cursor, iTradeSize);
+ }
+
+ public void setTradeSize(RecordCursor cursor, double tradeSize) {
+ if (iTradeSize < 0)
+ return;
+ setAsDouble(cursor, iTradeSize, tradeSize);
+ }
+
+ public int getTradeSizeDecimal(RecordCursor cursor) {
+ if (iTradeSize < 0)
+ return 0;
+ return getAsTinyDecimal(cursor, iTradeSize);
+ }
+
+ public void setTradeSizeDecimal(RecordCursor cursor, int tradeSize) {
+ if (iTradeSize < 0)
+ return;
+ setAsTinyDecimal(cursor, iTradeSize, tradeSize);
+ }
+
+ public long getTradeSizeWideDecimal(RecordCursor cursor) {
+ if (iTradeSize < 0)
+ return 0;
+ return getAsWideDecimal(cursor, iTradeSize);
+ }
+
+ public void setTradeSizeWideDecimal(RecordCursor cursor, long tradeSize) {
+ if (iTradeSize < 0)
+ return;
+ setAsWideDecimal(cursor, iTradeSize, tradeSize);
+ }
+
@Deprecated
public String getMMIDString(RecordCursor cursor) {
if (iMarketMaker < 0)
diff --git a/dxfeed-impl/src/main/java/com/dxfeed/event/market/impl/SpreadOrderMapping.java b/dxfeed-impl/src/main/java/com/dxfeed/event/market/impl/SpreadOrderMapping.java
index 5b955cd11..fc289f3f6 100644
--- a/dxfeed-impl/src/main/java/com/dxfeed/event/market/impl/SpreadOrderMapping.java
+++ b/dxfeed-impl/src/main/java/com/dxfeed/event/market/impl/SpreadOrderMapping.java
@@ -22,11 +22,17 @@ public class SpreadOrderMapping extends OrderBaseMapping {
private final int iTime;
private final int iSequence;
private final int iTimeNanoPart;
+ private final int iActionTime;
+ private final int iOrderId;
+ private final int iAuxOrderId;
private final int iPrice;
private final int iSize;
private final int iExecutedSize;
private final int iCount;
private final int iFlags;
+ private final int iTradeId;
+ private final int iTradePrice;
+ private final int iTradeSize;
private final int oSpreadSymbol;
public SpreadOrderMapping(DataRecord record) {
@@ -35,11 +41,17 @@ public SpreadOrderMapping(DataRecord record) {
iTime = MappingUtil.findIntField(record, "Time", true);
iSequence = MappingUtil.findIntField(record, "Sequence", true);
iTimeNanoPart = MappingUtil.findIntField(record, "TimeNanoPart", false);
+ iActionTime = MappingUtil.findIntField(record, "ActionTime", false);
+ iOrderId = MappingUtil.findIntField(record, "OrderId", false);
+ iAuxOrderId = MappingUtil.findIntField(record, "AuxOrderId", false);
iPrice = findIntField("Price", true);
iSize = findIntField("Size", true);
iExecutedSize = findIntField("ExecutedSize", false);
iCount = findIntField("Count", false);
iFlags = MappingUtil.findIntField(record, "Flags", true);
+ iTradeId = MappingUtil.findIntField(record, "TradeId", false);
+ iTradePrice = findIntField("TradePrice", false);
+ iTradeSize = findIntField("TradeSize", false);
oSpreadSymbol = MappingUtil.findObjField(record, "SpreadSymbol", true);
}
@@ -87,6 +99,54 @@ public void setTimeNanoPart(RecordCursor cursor, int timeNanoPart) {
setInt(cursor, iTimeNanoPart, timeNanoPart);
}
+ public long getActionTimeMillis(RecordCursor cursor) {
+ if (iActionTime < 0)
+ return 0;
+ return getLong(cursor, iActionTime);
+ }
+
+ public void setActionTimeMillis(RecordCursor cursor, long actionTime) {
+ if (iActionTime < 0)
+ return;
+ setLong(cursor, iActionTime, actionTime);
+ }
+
+ public int getActionTimeSeconds(RecordCursor cursor) {
+ if (iActionTime < 0)
+ return 0;
+ return TimeUtil.getSecondsFromTime(getLong(cursor, iActionTime));
+ }
+
+ public void setActionTimeSeconds(RecordCursor cursor, int actionTime) {
+ if (iActionTime < 0)
+ return;
+ setLong(cursor, iActionTime, actionTime * 1000L);
+ }
+
+ public long getOrderId(RecordCursor cursor) {
+ if (iOrderId < 0)
+ return 0;
+ return getLong(cursor, iOrderId);
+ }
+
+ public void setOrderId(RecordCursor cursor, long orderId) {
+ if (iOrderId < 0)
+ return;
+ setLong(cursor, iOrderId, orderId);
+ }
+
+ public long getAuxOrderId(RecordCursor cursor) {
+ if (iAuxOrderId < 0)
+ return 0;
+ return getLong(cursor, iAuxOrderId);
+ }
+
+ public void setAuxOrderId(RecordCursor cursor, long auxOrderId) {
+ if (iAuxOrderId < 0)
+ return;
+ setLong(cursor, iAuxOrderId, auxOrderId);
+ }
+
public double getPrice(RecordCursor cursor) {
return getAsDouble(cursor, iPrice);
}
@@ -255,6 +315,90 @@ public void setFlags(RecordCursor cursor, int flags) {
setInt(cursor, iFlags, flags);
}
+ public long getTradeId(RecordCursor cursor) {
+ if (iTradeId < 0)
+ return 0;
+ return getLong(cursor, iTradeId);
+ }
+
+ public void setTradeId(RecordCursor cursor, long tradeId) {
+ if (iTradeId < 0)
+ return;
+ setLong(cursor, iTradeId, tradeId);
+ }
+
+ public double getTradePrice(RecordCursor cursor) {
+ if (iTradePrice < 0)
+ return Double.NaN;
+ return getAsDouble(cursor, iTradePrice);
+ }
+
+ public void setTradePrice(RecordCursor cursor, double tradePrice) {
+ if (iTradePrice < 0)
+ return;
+ setAsDouble(cursor, iTradePrice, tradePrice);
+ }
+
+ public int getTradePriceDecimal(RecordCursor cursor) {
+ if (iTradePrice < 0)
+ return 0;
+ return getAsTinyDecimal(cursor, iTradePrice);
+ }
+
+ public void setTradePriceDecimal(RecordCursor cursor, int tradePrice) {
+ if (iTradePrice < 0)
+ return;
+ setAsTinyDecimal(cursor, iTradePrice, tradePrice);
+ }
+
+ public long getTradePriceWideDecimal(RecordCursor cursor) {
+ if (iTradePrice < 0)
+ return 0;
+ return getAsWideDecimal(cursor, iTradePrice);
+ }
+
+ public void setTradePriceWideDecimal(RecordCursor cursor, long tradePrice) {
+ if (iTradePrice < 0)
+ return;
+ setAsWideDecimal(cursor, iTradePrice, tradePrice);
+ }
+
+ public double getTradeSize(RecordCursor cursor) {
+ if (iTradeSize < 0)
+ return Double.NaN;
+ return getAsDouble(cursor, iTradeSize);
+ }
+
+ public void setTradeSize(RecordCursor cursor, double tradeSize) {
+ if (iTradeSize < 0)
+ return;
+ setAsDouble(cursor, iTradeSize, tradeSize);
+ }
+
+ public int getTradeSizeDecimal(RecordCursor cursor) {
+ if (iTradeSize < 0)
+ return 0;
+ return getAsTinyDecimal(cursor, iTradeSize);
+ }
+
+ public void setTradeSizeDecimal(RecordCursor cursor, int tradeSize) {
+ if (iTradeSize < 0)
+ return;
+ setAsTinyDecimal(cursor, iTradeSize, tradeSize);
+ }
+
+ public long getTradeSizeWideDecimal(RecordCursor cursor) {
+ if (iTradeSize < 0)
+ return 0;
+ return getAsWideDecimal(cursor, iTradeSize);
+ }
+
+ public void setTradeSizeWideDecimal(RecordCursor cursor, long tradeSize) {
+ if (iTradeSize < 0)
+ return;
+ setAsWideDecimal(cursor, iTradeSize, tradeSize);
+ }
+
public String getSpreadSymbol(RecordCursor cursor) {
return (String) getObj(cursor, oSpreadSymbol);
}
diff --git a/dxfeed-impl/src/main/java/com/dxfeed/event/option/OptionFactoryImpl.java b/dxfeed-impl/src/main/java/com/dxfeed/event/option/OptionFactoryImpl.java
index efb976246..918b65bb5 100644
--- a/dxfeed-impl/src/main/java/com/dxfeed/event/option/OptionFactoryImpl.java
+++ b/dxfeed-impl/src/main/java/com/dxfeed/event/option/OptionFactoryImpl.java
@@ -36,7 +36,7 @@ public final class OptionFactoryImpl extends EventDelegateFactory implements Rec
// BEGIN: CODE AUTOMATICALLY GENERATED: DO NOT MODIFY. IT IS REGENERATED BY com.dxfeed.api.codegen.ImplCodeGen
@Override
public void buildScheme(SchemeBuilder builder) {
- builder.addOptionalField("Greeks", "Time", SerialFieldType.TIME, "Greeks", "Time", true, SchemeFieldTime.FIRST_TIME_INT_FIELD);
+ builder.addOptionalField("Greeks", "Time", SerialFieldType.TIME_SECONDS, "Greeks", "Time", true, SchemeFieldTime.FIRST_TIME_INT_FIELD);
builder.addOptionalField("Greeks", "Sequence", SerialFieldType.SEQUENCE, "Greeks", "Sequence", true, SchemeFieldTime.SECOND_TIME_INT_FIELD);
builder.addRequiredField("Greeks", "Greeks.Price", select(SerialFieldType.DECIMAL, "dxscheme.price"));
builder.addRequiredField("Greeks", "Volatility", select(SerialFieldType.DECIMAL));
@@ -46,7 +46,7 @@ public void buildScheme(SchemeBuilder builder) {
builder.addRequiredField("Greeks", "Rho", select(SerialFieldType.DECIMAL));
builder.addRequiredField("Greeks", "Vega", select(SerialFieldType.DECIMAL));
- builder.addRequiredField("TheoPrice", "Theo.Time", SerialFieldType.TIME, SchemeFieldTime.FIRST_TIME_INT_FIELD);
+ builder.addRequiredField("TheoPrice", "Theo.Time", SerialFieldType.TIME_SECONDS, SchemeFieldTime.FIRST_TIME_INT_FIELD);
builder.addOptionalField("TheoPrice", "Theo.Sequence", SerialFieldType.SEQUENCE, "TheoPrice", "Sequence", true, SchemeFieldTime.SECOND_TIME_INT_FIELD);
builder.addRequiredField("TheoPrice", "Theo.Price", select(SerialFieldType.DECIMAL, "dxscheme.price"));
builder.addRequiredField("TheoPrice", "Theo.UnderlyingPrice", select(SerialFieldType.DECIMAL, "dxscheme.price"));
@@ -55,7 +55,7 @@ public void buildScheme(SchemeBuilder builder) {
builder.addOptionalField("TheoPrice", "Theo.Dividend", select(SerialFieldType.DECIMAL), "TheoPrice", "Dividend", true);
builder.addOptionalField("TheoPrice", "Theo.Interest", select(SerialFieldType.DECIMAL), "TheoPrice", "Interest", true);
- builder.addOptionalField("Underlying", "Time", SerialFieldType.TIME, "Underlying", "Time", true, SchemeFieldTime.FIRST_TIME_INT_FIELD);
+ builder.addOptionalField("Underlying", "Time", SerialFieldType.TIME_SECONDS, "Underlying", "Time", true, SchemeFieldTime.FIRST_TIME_INT_FIELD);
builder.addOptionalField("Underlying", "Sequence", SerialFieldType.SEQUENCE, "Underlying", "Sequence", true, SchemeFieldTime.SECOND_TIME_INT_FIELD);
builder.addOptionalField("Underlying", "Volatility", select(SerialFieldType.DECIMAL), "Underlying", "Volatility", true);
builder.addOptionalField("Underlying", "FrontVolatility", select(SerialFieldType.DECIMAL), "Underlying", "FrontVolatility", true);
@@ -66,7 +66,7 @@ public void buildScheme(SchemeBuilder builder) {
builder.addOptionalField("Series", "Void", SerialFieldType.VOID, "Series", "Void", true, SchemeFieldTime.FIRST_TIME_INT_FIELD);
builder.addOptionalField("Series", "Index", SerialFieldType.COMPACT_INT, "Series", "Index", true, SchemeFieldTime.SECOND_TIME_INT_FIELD);
- builder.addOptionalField("Series", "Time", SerialFieldType.TIME, "Series", "Time", true);
+ builder.addOptionalField("Series", "Time", SerialFieldType.TIME_SECONDS, "Series", "Time", true);
builder.addOptionalField("Series", "Sequence", SerialFieldType.SEQUENCE, "Series", "Sequence", true);
builder.addRequiredField("Series", "Expiration", SerialFieldType.DATE);
builder.addRequiredField("Series", "Volatility", select(SerialFieldType.DECIMAL));
diff --git a/dxfeed-ipf-filter/pom.xml b/dxfeed-ipf-filter/pom.xml
index d432d784c..dda7836af 100644
--- a/dxfeed-ipf-filter/pom.xml
+++ b/dxfeed-ipf-filter/pom.xml
@@ -14,7 +14,7 @@
QD
com.devexperts.qd
- 3.291
+ 3.292
4.0.0
diff --git a/dxfeed-news/pom.xml b/dxfeed-news/pom.xml
index 63a76c562..13bc359ae 100644
--- a/dxfeed-news/pom.xml
+++ b/dxfeed-news/pom.xml
@@ -14,7 +14,7 @@
QD
com.devexperts.qd
- 3.291
+ 3.292
4.0.0
diff --git a/dxfeed-ondemand/pom.xml b/dxfeed-ondemand/pom.xml
index 8944517d7..8ac930f76 100644
--- a/dxfeed-ondemand/pom.xml
+++ b/dxfeed-ondemand/pom.xml
@@ -14,7 +14,7 @@
QD
com.devexperts.qd
- 3.291
+ 3.292
4.0.0
diff --git a/dxfeed-plotter/pom.xml b/dxfeed-plotter/pom.xml
index 0740769a0..0f8a8ba64 100644
--- a/dxfeed-plotter/pom.xml
+++ b/dxfeed-plotter/pom.xml
@@ -14,7 +14,7 @@
QD
com.devexperts.qd
- 3.291
+ 3.292
4.0.0
diff --git a/dxfeed-promise/pom.xml b/dxfeed-promise/pom.xml
index 5edfbe225..60714a8b2 100644
--- a/dxfeed-promise/pom.xml
+++ b/dxfeed-promise/pom.xml
@@ -14,7 +14,7 @@
QD
com.devexperts.qd
- 3.291
+ 3.292
4.0.0
diff --git a/dxfeed-samples/pom.xml b/dxfeed-samples/pom.xml
index affb00ba6..2fd3d7e46 100644
--- a/dxfeed-samples/pom.xml
+++ b/dxfeed-samples/pom.xml
@@ -14,13 +14,13 @@
QD
com.devexperts.qd
- 3.291
+ 3.292
../pom.xml
4.0.0
dxfeed-samples
- 3.291
+ 3.292
jar
diff --git a/dxfeed-tools/pom.xml b/dxfeed-tools/pom.xml
index bf538b7a0..3ff01f36d 100644
--- a/dxfeed-tools/pom.xml
+++ b/dxfeed-tools/pom.xml
@@ -14,7 +14,7 @@
QD
com.devexperts.qd
- 3.291
+ 3.292
../pom.xml
4.0.0
diff --git a/dxfeed-viewer-installer/pom.xml b/dxfeed-viewer-installer/pom.xml
index 1f7ff3418..f376830c2 100644
--- a/dxfeed-viewer-installer/pom.xml
+++ b/dxfeed-viewer-installer/pom.xml
@@ -14,7 +14,7 @@
QD
com.devexperts.qd
- 3.291
+ 3.292
../pom.xml
4.0.0
diff --git a/dxfeed-viewer/pom.xml b/dxfeed-viewer/pom.xml
index 701933dee..9faaa8a3d 100644
--- a/dxfeed-viewer/pom.xml
+++ b/dxfeed-viewer/pom.xml
@@ -14,7 +14,7 @@
QD
com.devexperts.qd
- 3.291
+ 3.292
../pom.xml
4.0.0
diff --git a/dxfeed-webservice-impl/pom.xml b/dxfeed-webservice-impl/pom.xml
index 76831d189..20b5ae3a3 100644
--- a/dxfeed-webservice-impl/pom.xml
+++ b/dxfeed-webservice-impl/pom.xml
@@ -14,7 +14,7 @@
QD
com.devexperts.qd
- 3.291
+ 3.292
4.0.0
diff --git a/dxfeed-webservice/pom.xml b/dxfeed-webservice/pom.xml
index 45f2c06a9..bfc03f21f 100644
--- a/dxfeed-webservice/pom.xml
+++ b/dxfeed-webservice/pom.xml
@@ -14,7 +14,7 @@
QD
com.devexperts.qd
- 3.291
+ 3.292
4.0.0
diff --git a/dxlib-benchmark/pom.xml b/dxlib-benchmark/pom.xml
index 32bbecc6b..bd9b37706 100644
--- a/dxlib-benchmark/pom.xml
+++ b/dxlib-benchmark/pom.xml
@@ -2,7 +2,7 @@
QD
com.devexperts.qd
- 3.291
+ 3.292
../pom.xml
4.0.0
diff --git a/dxlib-qd-sandbox/pom.xml b/dxlib-qd-sandbox/pom.xml
index 143f8d4dc..a8b11d723 100644
--- a/dxlib-qd-sandbox/pom.xml
+++ b/dxlib-qd-sandbox/pom.xml
@@ -14,7 +14,7 @@
QD
com.devexperts.qd
- 3.291
+ 3.292
../pom.xml
4.0.0
diff --git a/dxlib/pom.xml b/dxlib/pom.xml
index 4ffdaef8b..56dba8177 100644
--- a/dxlib/pom.xml
+++ b/dxlib/pom.xml
@@ -14,7 +14,7 @@
QD
com.devexperts.qd
- 3.291
+ 3.292
../pom.xml
4.0.0
diff --git a/license/pom.xml b/license/pom.xml
index ea1023d6b..42b2981b0 100644
--- a/license/pom.xml
+++ b/license/pom.xml
@@ -14,7 +14,7 @@
QD
com.devexperts.qd
- 3.291
+ 3.292
../pom.xml
4.0.0
diff --git a/mars-sample/pom.xml b/mars-sample/pom.xml
index 631ce2a51..82449e3ff 100644
--- a/mars-sample/pom.xml
+++ b/mars-sample/pom.xml
@@ -14,7 +14,7 @@
QD
com.devexperts.qd
- 3.291
+ 3.292
../pom.xml
4.0.0
diff --git a/mars/pom.xml b/mars/pom.xml
index 4a6df4ada..a5b1ff921 100644
--- a/mars/pom.xml
+++ b/mars/pom.xml
@@ -14,7 +14,7 @@
QD
com.devexperts.qd
- 3.291
+ 3.292
../pom.xml
4.0.0
diff --git a/pom.xml b/pom.xml
index 4f2e3f9c6..02d24ecec 100644
--- a/pom.xml
+++ b/pom.xml
@@ -16,7 +16,7 @@
com.devexperts.qd
QD
pom
- 3.291
+ 3.292
2002
diff --git a/proto-sample/pom.xml b/proto-sample/pom.xml
index 305960f40..a7dffb469 100644
--- a/proto-sample/pom.xml
+++ b/proto-sample/pom.xml
@@ -14,7 +14,7 @@
QD
com.devexperts.qd
- 3.291
+ 3.292
../pom.xml
4.0.0
diff --git a/proto-ssl/pom.xml b/proto-ssl/pom.xml
index bb7d94532..f4fcacf82 100644
--- a/proto-ssl/pom.xml
+++ b/proto-ssl/pom.xml
@@ -14,7 +14,7 @@
QD
com.devexperts.qd
- 3.291
+ 3.292
../pom.xml
4.0.0
diff --git a/proto/pom.xml b/proto/pom.xml
index 6f965203c..12491fbc8 100644
--- a/proto/pom.xml
+++ b/proto/pom.xml
@@ -14,7 +14,7 @@
QD
com.devexperts.qd
- 3.291
+ 3.292
../pom.xml
4.0.0
diff --git a/qd-core/pom.xml b/qd-core/pom.xml
index 0e1cb9939..d1ebe08c1 100644
--- a/qd-core/pom.xml
+++ b/qd-core/pom.xml
@@ -14,7 +14,7 @@
QD
com.devexperts.qd
- 3.291
+ 3.292
../pom.xml
4.0.0
diff --git a/qd-core/src/main/java/com/devexperts/qd/SerialFieldType.java b/qd-core/src/main/java/com/devexperts/qd/SerialFieldType.java
index 414588231..06be347e0 100644
--- a/qd-core/src/main/java/com/devexperts/qd/SerialFieldType.java
+++ b/qd-core/src/main/java/com/devexperts/qd/SerialFieldType.java
@@ -16,23 +16,28 @@
import com.devexperts.qd.kit.CompactIntField;
import com.devexperts.qd.kit.DateField;
import com.devexperts.qd.kit.DecimalField;
+import com.devexperts.qd.kit.LongField;
import com.devexperts.qd.kit.MarshalledObjField;
import com.devexperts.qd.kit.PlainIntField;
import com.devexperts.qd.kit.SequenceField;
import com.devexperts.qd.kit.ShortStringField;
import com.devexperts.qd.kit.StringField;
-import com.devexperts.qd.kit.TimeField;
+import com.devexperts.qd.kit.TimeSecondsField;
+import com.devexperts.qd.kit.TimeMillisField;
import com.devexperts.qd.kit.VoidIntField;
import com.devexperts.qd.kit.WideDecimalField;
import static com.devexperts.qd.SerialFieldType.Bits.FLAG_CUSTOM_OBJECT;
import static com.devexperts.qd.SerialFieldType.Bits.FLAG_DATE;
import static com.devexperts.qd.SerialFieldType.Bits.FLAG_DECIMAL;
+import static com.devexperts.qd.SerialFieldType.Bits.FLAG_LONG;
import static com.devexperts.qd.SerialFieldType.Bits.FLAG_SEQUENCE;
import static com.devexperts.qd.SerialFieldType.Bits.FLAG_SERIAL_OBJECT;
import static com.devexperts.qd.SerialFieldType.Bits.FLAG_SHORT_STRING;
import static com.devexperts.qd.SerialFieldType.Bits.FLAG_STRING;
-import static com.devexperts.qd.SerialFieldType.Bits.FLAG_TIME;
+import static com.devexperts.qd.SerialFieldType.Bits.FLAG_TIME_MILLIS;
+import static com.devexperts.qd.SerialFieldType.Bits.FLAG_TIME_NANOS;
+import static com.devexperts.qd.SerialFieldType.Bits.FLAG_TIME_SECONDS;
import static com.devexperts.qd.SerialFieldType.Bits.FLAG_WIDE_DECIMAL;
import static com.devexperts.qd.SerialFieldType.Bits.ID_BYTE;
import static com.devexperts.qd.SerialFieldType.Bits.ID_BYTE_ARRAY;
@@ -63,9 +68,15 @@ public final class SerialFieldType {
public static final SerialFieldType DECIMAL = new SerialFieldType(ID_COMPACT_INT | FLAG_DECIMAL, "DECIMAL");
public static final SerialFieldType SHORT_STRING = new SerialFieldType(ID_COMPACT_INT | FLAG_SHORT_STRING, "SHORT_STRING");
- public static final SerialFieldType TIME = new SerialFieldType(ID_COMPACT_INT | FLAG_TIME, "TIME");
+ public static final SerialFieldType TIME_SECONDS = new SerialFieldType(ID_COMPACT_INT | FLAG_TIME_SECONDS, "TIME_SECONDS");
+ public static final SerialFieldType TIME_MILLIS = new SerialFieldType(ID_COMPACT_INT | FLAG_TIME_MILLIS, "TIME_MILLIS");
+ public static final SerialFieldType TIME_NANOS = new SerialFieldType(ID_COMPACT_INT | FLAG_TIME_NANOS, "TIME_NANOS"); // Reserved
+ /** @deprecated Use {@link #TIME_SECONDS} instead. */
+ @Deprecated
+ public static final SerialFieldType TIME = TIME_SECONDS;
public static final SerialFieldType SEQUENCE = new SerialFieldType(ID_COMPACT_INT | FLAG_SEQUENCE, "SEQUENCE");
public static final SerialFieldType DATE = new SerialFieldType(ID_COMPACT_INT | FLAG_DATE, "DATE");
+ public static final SerialFieldType LONG = new SerialFieldType(ID_COMPACT_INT | FLAG_LONG, "LONG");
public static final SerialFieldType WIDE_DECIMAL = new SerialFieldType(ID_COMPACT_INT | FLAG_WIDE_DECIMAL, "WIDE_DECIMAL");
public static final SerialFieldType STRING = new SerialFieldType(ID_BYTE_ARRAY | FLAG_STRING, "STRING");
public static final SerialFieldType CUSTOM_OBJECT = new SerialFieldType(ID_BYTE_ARRAY | FLAG_CUSTOM_OBJECT, "CUSTOM_OBJECT");
@@ -74,6 +85,7 @@ public final class SerialFieldType {
// Named instances (use same ids as standard ones)
// used by forNamedField method
+ private static final SerialFieldType ID = LONG.withName("ID");
private static final SerialFieldType MMID = SHORT_STRING.withName("MMID");
private static final SerialFieldType EXCHANGE = UTF_CHAR.withName("EXCHANGE");
private static final SerialFieldType PRICE = DECIMAL.withName("PRICE");
@@ -88,10 +100,10 @@ public final class SerialFieldType {
private static final SerialFieldType SALE_FLAGS = COMPACT_INT.withName("SALE_FLAGS");
private static final SerialFieldType PROFILE_FLAGS = COMPACT_INT.withName("PROFILE_FLAGS");
- private static final SerialFieldType BID_TIME = TIME.withName("BID_TIME");
- private static final SerialFieldType ASK_TIME = TIME.withName("ASK_TIME");
- private static final SerialFieldType TRADE_TIME = TIME.withName("TRADE_TIME");
- private static final SerialFieldType CANDLE_TIME = TIME.withName("CANDLE_TIME");
+ private static final SerialFieldType BID_TIME = TIME_SECONDS.withName("BID_TIME");
+ private static final SerialFieldType ASK_TIME = TIME_SECONDS.withName("ASK_TIME");
+ private static final SerialFieldType TRADE_TIME = TIME_SECONDS.withName("TRADE_TIME");
+ private static final SerialFieldType CANDLE_TIME = TIME_SECONDS.withName("CANDLE_TIME");
private static final SerialFieldType QUOTE_PRICE = DECIMAL.withName("QUOTE_PRICE");
private static final SerialFieldType TRADE_PRICE = DECIMAL.withName("TRADE_PRICE");
@@ -111,7 +123,8 @@ private SerialFieldType(int id, String name) {
this.id = id;
this.name = name;
this.isObject = (id & SERIAL_TYPE_MASK) == ID_BYTE_ARRAY || (id & SERIAL_TYPE_MASK) == ID_UTF_CHAR_ARRAY;
- this.isLong = (id & REPRESENTATION_MASK) == FLAG_WIDE_DECIMAL;
+ this.isLong = (id & REPRESENTATION_MASK) == FLAG_WIDE_DECIMAL || (id & REPRESENTATION_MASK) == FLAG_LONG ||
+ (id & REPRESENTATION_MASK) == FLAG_TIME_MILLIS;
if (isLong && isObject)
throw new IllegalArgumentException("conflicting type");
}
@@ -172,7 +185,7 @@ public SerialFieldType withName(String name) {
/**
* Returns a more specific serial type that shall be used for the field with the specified name.
- * For {@link #COMPACT_INT}, {@link #DECIMAL}, {@link #SHORT_STRING}, and {@link #TIME} base types this
+ * For {@link #COMPACT_INT}, {@link #DECIMAL}, {@link #SHORT_STRING}, and {@link #TIME_SECONDS} base types this
* method looks at the suffix of the name, for other base types just the base type itself is returned.
*/
public SerialFieldType forNamedField(String name) {
@@ -184,7 +197,7 @@ public SerialFieldType forNamedField(String name) {
private SerialFieldType forNamedFieldImpl(String name) {
// ----------- time fields -----------
- if (this == COMPACT_INT || this == TIME) {
+ if (this == COMPACT_INT || this == TIME_SECONDS) {
if (name.endsWith("Bid.Time"))
return BID_TIME;
if (name.endsWith("Ask.Time"))
@@ -195,7 +208,7 @@ private SerialFieldType forNamedFieldImpl(String name) {
return CANDLE_TIME;
}
if (this == COMPACT_INT && name.endsWith("Time"))
- return TIME;
+ return TIME_SECONDS;
// ----------- price fields -----------
if (this == DECIMAL) {
if (name.endsWith("Bid.Price") || name.endsWith("Ask.Price"))
@@ -209,7 +222,7 @@ private SerialFieldType forNamedFieldImpl(String name) {
if (name.endsWith("High.Price") || name.endsWith("Low.Price") || name.endsWith("Open.Price") || name.endsWith("Close.Price"))
return SUMMARY_PRICE;
if ((name.startsWith("Trade.") || name.startsWith("Candle.")) &&
- (name.endsWith("Open") || name.endsWith("High") || name.endsWith("Low") || name.endsWith("Close")) || name.endsWith("VWAP"))
+ (name.endsWith("Open") || name.endsWith("High") || name.endsWith("Low") || name.endsWith("Close") || name.endsWith("VWAP")))
{
return CANDLE_PRICE;
}
@@ -224,6 +237,8 @@ private SerialFieldType forNamedFieldImpl(String name) {
return DATE;
if (this == COMPACT_INT && name.endsWith("Sequence"))
return SEQUENCE;
+ if (this == LONG && name.endsWith("Id")) // Except DayId which is handled above
+ return ID;
if (this == SHORT_STRING && name.endsWith("MMID"))
return MMID;
if (this == UTF_CHAR && name.endsWith("Exchange"))
@@ -268,12 +283,16 @@ public DataIntField createDefaultIntInstance(int index, String name) {
return new DecimalField(index, name, this);
case FLAG_SHORT_STRING:
return new ShortStringField(index, name, this);
- case FLAG_TIME:
- return new TimeField(index, name);
+ case FLAG_TIME_SECONDS:
+ return new TimeSecondsField(index, name);
+ case FLAG_TIME_MILLIS:
+ return new TimeMillisField(index, name);
case FLAG_SEQUENCE:
return new SequenceField(index, name);
case FLAG_DATE:
return new DateField(index, name);
+ case FLAG_LONG:
+ return new LongField(index, name);
case FLAG_WIDE_DECIMAL:
return new WideDecimalField(index, name);
default:
@@ -348,11 +367,16 @@ private Bits() {} // do not create
public static final int FLAG_INT = 0x00; // plain int as int field
public static final int FLAG_DECIMAL = 0x10; // decimal representation as int field
public static final int FLAG_SHORT_STRING = 0x20; // short (up to 4-character) string representation as int field
- public static final int FLAG_TIME = 0x30; // time in seconds in this integer field
+ public static final int FLAG_TIME_SECONDS = 0x30; // time in seconds as integer field
+ @Deprecated
+ public static final int FLAG_TIME = FLAG_TIME_SECONDS;
public static final int FLAG_SEQUENCE = 0x40; // sequence in this integer fields (with top 10 bits representing millis)
public static final int FLAG_DATE = 0x50; // day id in this integer field
+ public static final int FLAG_LONG = 0x60; // plain long as two int fields
public static final int FLAG_WIDE_DECIMAL = 0x70; // WideDecimal representation as long field
public static final int FLAG_STRING = 0x80; // String representation as byte array (for ID_BYTE_ARRAY)
+ public static final int FLAG_TIME_MILLIS = 0x90; // time in millis as long field
+ public static final int FLAG_TIME_NANOS = 0xa0; // Reserved for future use: time in nanos as long field
public static final int FLAG_CUSTOM_OBJECT = 0xe0; // custom serialized object as byte array (for ID_BYTE_ARRAY)
public static final int FLAG_SERIAL_OBJECT = 0xf0; // serialized object as byte array (for ID_BYTE_ARRAY)
}
diff --git a/qd-core/src/main/java/com/devexperts/qd/impl/matrix/Distribution.java b/qd-core/src/main/java/com/devexperts/qd/impl/matrix/Distribution.java
index eb928d5f5..d8e0f3733 100644
--- a/qd-core/src/main/java/com/devexperts/qd/impl/matrix/Distribution.java
+++ b/qd-core/src/main/java/com/devexperts/qd/impl/matrix/Distribution.java
@@ -65,10 +65,10 @@ static Distribution getInstance() {
static final int UPDATED_SNIP_DIST_FLAG = 1 << 30;
/**
- * Set when this event turned on "snapshotBegin" flag in the history buffer (need to reset KNOWN_TIME).
- * Implies that {@code (eventFlags & SNAPSHOT_BEGIN) != 0}.
+ * Set when this event turned on "snapshotBegin" flag in the history buffer or forced snapshot retransmit.
+ * Need to reset KNOWN_TIME in agent. Implies that {@code (eventFlags & SNAPSHOT_BEGIN) != 0}.
*/
- static final int ENABLED_SNAPSHOT_MODE_DIST_FLAG = 1 << 29;
+ static final int SEND_SNAPSHOT_DIST_FLAG = 1 << 29;
/**
* Set when record was updated in HB (includes remove of an existing one).
diff --git a/qd-core/src/main/java/com/devexperts/qd/impl/matrix/History.java b/qd-core/src/main/java/com/devexperts/qd/impl/matrix/History.java
index 2f739ae20..9b35abd3a 100644
--- a/qd-core/src/main/java/com/devexperts/qd/impl/matrix/History.java
+++ b/qd-core/src/main/java/com/devexperts/qd/impl/matrix/History.java
@@ -15,9 +15,11 @@
import com.devexperts.qd.DataVisitor;
import com.devexperts.qd.HistorySubscriptionFilter;
import com.devexperts.qd.QDAgent;
+import com.devexperts.qd.QDFilter;
import com.devexperts.qd.QDHistory;
import com.devexperts.qd.SymbolCodec;
import com.devexperts.qd.impl.matrix.management.CollectorOperation;
+import com.devexperts.qd.kit.RecordOnlyFilter;
import com.devexperts.qd.ng.EventFlag;
import com.devexperts.qd.ng.RecordBuffer;
import com.devexperts.qd.ng.RecordCursor;
@@ -31,8 +33,8 @@
import com.devexperts.util.TimePeriod;
import static com.devexperts.qd.impl.matrix.Distribution.DEC_PENDING_COUNT_DIST_FLAG;
-import static com.devexperts.qd.impl.matrix.Distribution.ENABLED_SNAPSHOT_MODE_DIST_FLAG;
import static com.devexperts.qd.impl.matrix.Distribution.HAD_SNAPSHOT_DIST_FLAG;
+import static com.devexperts.qd.impl.matrix.Distribution.SEND_SNAPSHOT_DIST_FLAG;
import static com.devexperts.qd.impl.matrix.Distribution.TX_END_DIST_FLAG;
import static com.devexperts.qd.impl.matrix.Distribution.TX_SWEEP_DIST_FLAG;
import static com.devexperts.qd.impl.matrix.Distribution.UPDATED_RECORD_DIST_FLAG;
@@ -50,6 +52,13 @@ public class History extends Collector implements QDHistory {
static final long STATE_KEEP_TIME = TimePeriod.valueOf(SystemProperties.getProperty(
History.class, "stateKeepTime", "60s")).getTime();
+ //FIXME Abstraction leak here: History should not know about dxscheme and unconflated Order.
+ // Flag specifying "conflated" or "unconflated" mode for History (dxscheme.fob=false or true respectively).
+ static final boolean FOB_FLAG = SystemProperties.getBooleanProperty("dxscheme.fob", false);
+ // By default, all records except Order are conflated.
+ static final String CONFLATE_FILTER = SystemProperties.getProperty(History.class, "conflateFilter",
+ FOB_FLAG ? "!:Order*" : "*");
+
/*
* Event flags.
*/
@@ -161,17 +170,27 @@ public class History extends Collector implements QDHistory {
private final HistorySubscriptionFilter historyFilter;
+ // Filter specifying records to conflate in "unconflated" mode
+ private final QDFilter conflateFilter;
+
private ProcessVersionTracker processVersion = new ProcessVersionTracker();
//======================================= constructor =======================================
protected History(Builder> builder) {
+ this(builder, null);
+ }
+
+ protected History(Builder> builder, RecordOnlyFilter conflateFilter) {
super(builder, true, true);
HistorySubscriptionFilter historyFilter = builder.getHistoryFilter();
// If history filter is not specified in builder get it as service.
if (historyFilter == null)
historyFilter = builder.getScheme().getService(HistorySubscriptionFilter.class);
this.historyFilter = historyFilter;
+ if (conflateFilter == null)
+ conflateFilter = RecordOnlyFilter.valueOf(CONFLATE_FILTER, builder.getScheme());
+ this.conflateFilter = conflateFilter;
}
//======================================= methods =======================================
@@ -616,14 +635,17 @@ boolean processRecordSourceGLocked(Distributor distributor, Distribution dist, R
RecordCursor cursor;
while ((cursor = source.next()) != null) {
DataRecord record = cursor.getRecord();
+
int rid = getRid(record);
dist.countIncomingRecord(rid);
- int cipher = cursor.getCipher();
- String symbol = cursor.getSymbol();
if (!record.hasTime())
continue;
+
+ int cipher = cursor.getCipher();
+ String symbol = cursor.getSymbol();
if (storeEverything && !distributor.filter.accept(contract, record, cipher, symbol))
continue;
+
int key = getKey(cipher, symbol);
int tindex = tsub.getIndex(key, rid, 0);
// storeEverything support
@@ -668,9 +690,16 @@ boolean processRecordSourceGLocked(Distributor distributor, Distribution dist, R
/*
* (0) Turn on snapshot mode when we see SNAPSHOT_BEGIN/MODE flag (per QD-895).
*/
- if ((eventFlags & (SNAPSHOT_BEGIN | SNAPSHOT_MODE)) != 0 && hb.snapshotMode()) {
- // mark event that turned on snapshot mode (it will have to be processed by agents)
- distFlags |= ENABLED_SNAPSHOT_MODE_DIST_FLAG;
+ if ((eventFlags & (SNAPSHOT_BEGIN | SNAPSHOT_MODE)) != 0) {
+ if (hb.enterSnapshotModeFirstTime()) {
+ // mark event that turned on snapshot mode (it will have to be processed by agents)
+ distFlags |= SEND_SNAPSHOT_DIST_FLAG;
+ } else if (!conflateFilter.accept(cursor)) {
+ // We need to transparently retransmit the snapshot for unconflated events.
+ hb.enterSnapshotModeForUnconflated();
+ // mark event that triggered snapshot retransmission (it will have to be processed by agents)
+ distFlags |= SEND_SNAPSHOT_DIST_FLAG;
+ }
}
/*
* (1) Process transaction TX_PENDING logic. This logic is invoked even if subscription has changed and this
@@ -799,7 +828,7 @@ boolean processRecordSourceGLocked(Distributor distributor, Distribution dist, R
* this agent.
*/
if (sweepRemovePosition != endRemovePosition &&
- (!agent.useHistorySnapshot() || (distFlags & ENABLED_SNAPSHOT_MODE_DIST_FLAG) == 0))
+ (!agent.useHistorySnapshot() || (distFlags & SEND_SNAPSHOT_DIST_FLAG) == 0))
{
/*
* Submit snapshot-removed records (if subscribed). Note, that they constitute a part of
@@ -832,7 +861,7 @@ boolean processRecordSourceGLocked(Distributor distributor, Distribution dist, R
if (time >= timeSub && // (1)
((distFlags & (UPDATED_RECORD_DIST_FLAG | UPDATED_SNIP_DIST_FLAG)) != 0) || // (1a, 1b)
agent.useHistorySnapshot() &&
- (((distFlags & (TX_END_DIST_FLAG | ENABLED_SNAPSHOT_MODE_DIST_FLAG)) != 0) || // (2a, 2b)
+ (((distFlags & (TX_END_DIST_FLAG | SEND_SNAPSHOT_DIST_FLAG)) != 0) || // (2a, 2b)
(time < prevSnapshotTime && prevSnapshotTime > timeSub && hb.wasEverSnapshotMode()))) // (2c)
{
dist.add(nagent, position, distFlags, rid);
@@ -953,7 +982,7 @@ int processAgentDataUpdate(Distribution dist, RecordSource buffer, Agent agent)
* then reset agent's known time, so that it starts delivering snapshot from HB and clear everything from
* the local buffer (which might break snapshot consistency)
*/
- if (agent.useHistorySnapshot() && (distFlags & ENABLED_SNAPSHOT_MODE_DIST_FLAG) != 0) {
+ if (agent.useHistorySnapshot() && (distFlags & SEND_SNAPSHOT_DIST_FLAG) != 0) {
timeKnown = Long.MAX_VALUE;
asub.setLong(aindex + TIME_KNOWN, timeKnown);
// Drop everything queued in agent buffer
@@ -1099,7 +1128,7 @@ int processAgentDataUpdate(Distribution dist, RecordSource buffer, Agent agent)
* 2) event was received by history buffer in snapshot state
*
* This is so, because here we have an event that updates already retrieved part of the snapshot
- * (time >= timeKnow) which may have happened after an update to the part of the snapshot that is yet
+ * (time >= timeKnown) which may have happened after an update to the part of the snapshot that is yet
* to be retrieved, so the resulting snapshot may end up being inconsistent
* (see HistoryTxTest.testPartialRetrieveUpdateInconsistency)
*
@@ -1125,7 +1154,9 @@ int processAgentDataUpdate(Distribution dist, RecordSource buffer, Agent agent)
*/
if (lastRecordInBuffer) {
RecordCursor writeCursor = agent.buffer.writeCursorAtPersistentPosition(lastRecord);
- if (writeCursor.getTime() == (virtualTime ? VIRTUAL_TIME : time)) {
+ if (writeCursor.getTime() == (virtualTime ? VIRTUAL_TIME : time) &&
+ conflateFilter.accept(writeCursor))
+ {
// Conflate update to the most recently updated item and that's it
conflateLastRecord(cursor, writeCursor, virtualTime, eventFlags);
continue;
diff --git a/qd-core/src/main/java/com/devexperts/qd/impl/matrix/HistoryBuffer.java b/qd-core/src/main/java/com/devexperts/qd/impl/matrix/HistoryBuffer.java
index 37f5eb282..5ecd305aa 100644
--- a/qd-core/src/main/java/com/devexperts/qd/impl/matrix/HistoryBuffer.java
+++ b/qd-core/src/main/java/com/devexperts/qd/impl/matrix/HistoryBuffer.java
@@ -532,15 +532,19 @@ void resetSnapshot() {
// (0) Is invoked from processRecordSource when SNAPSHOT_BEGIN/MODE flag is set
// Returns true when EVER_SNAPSHOT_MODE_FLAG is just set
- boolean snapshotMode() {
+ boolean enterSnapshotModeFirstTime() {
assert validTimes();
if (wasEverSnapshotMode())
return false;
- // pretend as if we've see the actual SNAPSHOT_BEGIN (even if not).
+ // pretend as if we have seen the actual SNAPSHOT_BEGIN (even if not).
flags |= EVER_SNAPSHOT_MODE_FLAG | SNAPSHOT_BEGIN_SEEN_FLAG;
return true;
}
+ void enterSnapshotModeForUnconflated() {
+ flags |= EVER_SNAPSHOT_MODE_FLAG | SNAPSHOT_BEGIN_SEEN_FLAG;
+ }
+
// (1) Is invoked from processRecordSource with the value of TX_PENDING flag
// returns true when this event is the END of transaction (TX_END)
// Note, txPending flag will have to cleared later by invoking txEnd method.
@@ -568,13 +572,9 @@ boolean updateExplicitTx(boolean txPending) {
void snapshotBegin() {
snapshotTime = Long.MAX_VALUE;
assert validTimes();
- if (!wasSnapshotBeginSeen()) {
- // set snapshot begin flag first time we see it
- flags |= SNAPSHOT_BEGIN_SEEN_FLAG;
- } else {
- // we already have snapshot -- reset snapshot end flag
- flags &= ~SNAPSHOT_END_SEEN_FLAG;
- }
+
+ flags |= SNAPSHOT_BEGIN_SEEN_FLAG;
+ flags &= ~SNAPSHOT_END_SEEN_FLAG;
}
// (3) Is invoked from processRecordSource when SNAPSHOT_SNIP flag is set
diff --git a/qd-core/src/main/java/com/devexperts/qd/kit/DateField.java b/qd-core/src/main/java/com/devexperts/qd/kit/DateField.java
index f38e19cf9..030ea59d1 100644
--- a/qd-core/src/main/java/com/devexperts/qd/kit/DateField.java
+++ b/qd-core/src/main/java/com/devexperts/qd/kit/DateField.java
@@ -16,7 +16,7 @@
public class DateField extends CompactIntField {
public DateField(int index, String name) {
- super(index, name, SerialFieldType.DATE.forNamedField(name));
+ this(index, name, SerialFieldType.DATE.forNamedField(name));
}
public DateField(int index, String name, SerialFieldType serialType) {
diff --git a/qd-core/src/main/java/com/devexperts/qd/kit/LongField.java b/qd-core/src/main/java/com/devexperts/qd/kit/LongField.java
new file mode 100644
index 000000000..06223e97d
--- /dev/null
+++ b/qd-core/src/main/java/com/devexperts/qd/kit/LongField.java
@@ -0,0 +1,82 @@
+/*
+ * !++
+ * QDS - Quick Data Signalling Library
+ * !-
+ * Copyright (C) 2002 - 2020 Devexperts LLC
+ * !-
+ * 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/.
+ * !__
+ */
+package com.devexperts.qd.kit;
+
+import com.devexperts.io.BufferedInput;
+import com.devexperts.io.BufferedOutput;
+import com.devexperts.qd.SerialFieldType;
+import com.devexperts.qd.ng.RecordCursor;
+
+import java.io.IOException;
+
+/**
+ * The LongField
represents a long integer field with compact serialized form.
+ */
+public class LongField extends CompactIntField {
+ public LongField(int index, String name) {
+ this(index, name, SerialFieldType.LONG.forNamedField(name));
+ }
+
+ public LongField(int index, String name, SerialFieldType serialType) {
+ super(index, name, serialType);
+ if (!serialType.hasSameRepresentationAs(SerialFieldType.LONG))
+ throw new IllegalArgumentException("Invalid serialType: " + serialType);
+ }
+
+ @Override
+ public String getString(RecordCursor cursor) {
+ return toStringLong(cursor.getLong(getIndex()));
+ }
+
+ @Override
+ public void setString(RecordCursor cursor, String value) {
+ cursor.setLong(getIndex(), parseStringLong(value));
+ }
+
+ @Override
+ public void write(BufferedOutput out, RecordCursor cursor) throws IOException {
+ out.writeCompactLong(cursor.getLong(getIndex()));
+ }
+
+ @Override
+ public void read(BufferedInput in, RecordCursor cursor) throws IOException {
+ cursor.setLong(getIndex(), in.readCompactLong());
+ }
+
+ @Override
+ public String toString(int value) {
+ return toStringLong(value);
+ }
+
+ @Override
+ public int parseString(String value) {
+ return (int) parseStringLong(value);
+ }
+
+ @Override
+ public double toDouble(int value) {
+ return value;
+ }
+
+ @Override
+ public int toInt(double value) {
+ return (int) value;
+ }
+
+ protected String toStringLong(long value) {
+ return Long.toString(value);
+ }
+
+ protected long parseStringLong(String value) {
+ return Long.parseLong(value);
+ }
+}
diff --git a/qd-core/src/main/java/com/devexperts/qd/kit/SequenceField.java b/qd-core/src/main/java/com/devexperts/qd/kit/SequenceField.java
index c06c03b3d..6bf0d9fc8 100644
--- a/qd-core/src/main/java/com/devexperts/qd/kit/SequenceField.java
+++ b/qd-core/src/main/java/com/devexperts/qd/kit/SequenceField.java
@@ -18,7 +18,7 @@ public class SequenceField extends CompactIntField {
private static final int SEQUENCE_MASK = (1 << MILLIS_SHIFT) - 1;
public SequenceField(int index, String name) {
- super(index, name, SerialFieldType.SEQUENCE.forNamedField(name));
+ this(index, name, SerialFieldType.SEQUENCE.forNamedField(name));
}
public SequenceField(int index, String name, SerialFieldType serialType) {
diff --git a/qd-core/src/main/java/com/devexperts/qd/kit/TimeField.java b/qd-core/src/main/java/com/devexperts/qd/kit/TimeField.java
index 54b38ddbe..ed565f2fd 100644
--- a/qd-core/src/main/java/com/devexperts/qd/kit/TimeField.java
+++ b/qd-core/src/main/java/com/devexperts/qd/kit/TimeField.java
@@ -14,14 +14,18 @@
import com.devexperts.qd.SerialFieldType;
import com.devexperts.util.TimeFormat;
+/**
+ * @deprecated Use {@link TimeSecondsField} instead.
+ */
+@Deprecated()
public class TimeField extends CompactIntField {
public TimeField(int index, String name) {
- super(index, name, SerialFieldType.TIME.forNamedField(name));
+ this(index, name, SerialFieldType.TIME_SECONDS.forNamedField(name));
}
public TimeField(int index, String name, SerialFieldType serialType) {
super(index, name, serialType);
- if (!serialType.hasSameRepresentationAs(SerialFieldType.TIME))
+ if (!serialType.hasSameRepresentationAs(SerialFieldType.TIME_SECONDS))
throw new IllegalArgumentException("Invalid serialType: " + serialType);
}
diff --git a/qd-core/src/main/java/com/devexperts/qd/kit/TimeMillisField.java b/qd-core/src/main/java/com/devexperts/qd/kit/TimeMillisField.java
new file mode 100644
index 000000000..b7e875bac
--- /dev/null
+++ b/qd-core/src/main/java/com/devexperts/qd/kit/TimeMillisField.java
@@ -0,0 +1,80 @@
+/*
+ * !++
+ * QDS - Quick Data Signalling Library
+ * !-
+ * Copyright (C) 2002 - 2020 Devexperts LLC
+ * !-
+ * 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/.
+ * !__
+ */
+package com.devexperts.qd.kit;
+
+import com.devexperts.io.BufferedInput;
+import com.devexperts.io.BufferedOutput;
+import com.devexperts.qd.SerialFieldType;
+import com.devexperts.qd.ng.RecordCursor;
+import com.devexperts.util.TimeFormat;
+
+import java.io.IOException;
+
+public class TimeMillisField extends CompactIntField {
+ public TimeMillisField(int index, String name) {
+ this(index, name, SerialFieldType.TIME_MILLIS.forNamedField(name));
+ }
+
+ public TimeMillisField(int index, String name, SerialFieldType serialType) {
+ super(index, name, serialType);
+ if (!serialType.hasSameRepresentationAs(SerialFieldType.TIME_MILLIS))
+ throw new IllegalArgumentException("Invalid serialType: " + serialType);
+ }
+
+ @Override
+ public String getString(RecordCursor cursor) {
+ return toStringLong(cursor.getLong(getIndex()));
+ }
+
+ @Override
+ public void setString(RecordCursor cursor, String value) {
+ cursor.setLong(getIndex(), parseStringLong(value));
+ }
+
+ @Override
+ public void write(BufferedOutput out, RecordCursor cursor) throws IOException {
+ out.writeCompactLong(cursor.getLong(getIndex()));
+ }
+
+ @Override
+ public void read(BufferedInput in, RecordCursor cursor) throws IOException {
+ cursor.setLong(getIndex(), in.readCompactLong());
+ }
+
+ @Override
+ public String toString(int value) {
+ return toStringLong(value);
+ }
+
+ @Override
+ public int parseString(String value) {
+ return (int) parseStringLong(value);
+ }
+
+ @Override
+ public double toDouble(int value) {
+ return value;
+ }
+
+ @Override
+ public int toInt(double value) {
+ return (int) value;
+ }
+
+ protected String toStringLong(long value) {
+ return TimeFormat.DEFAULT.withTimeZone().withMillis().format(value);
+ }
+
+ protected long parseStringLong(String value) {
+ return TimeFormat.DEFAULT.parse(value).getTime();
+ }
+}
diff --git a/qd-core/src/main/java/com/devexperts/qd/kit/TimeSecondsField.java b/qd-core/src/main/java/com/devexperts/qd/kit/TimeSecondsField.java
new file mode 100644
index 000000000..55cd7ea58
--- /dev/null
+++ b/qd-core/src/main/java/com/devexperts/qd/kit/TimeSecondsField.java
@@ -0,0 +1,25 @@
+/*
+ * !++
+ * QDS - Quick Data Signalling Library
+ * !-
+ * Copyright (C) 2002 - 2020 Devexperts LLC
+ * !-
+ * 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/.
+ * !__
+ */
+package com.devexperts.qd.kit;
+
+import com.devexperts.qd.SerialFieldType;
+
+@SuppressWarnings("deprecation")
+public class TimeSecondsField extends TimeField {
+ public TimeSecondsField(int index, String name) {
+ super(index, name);
+ }
+
+ public TimeSecondsField(int index, String name, SerialFieldType serialType) {
+ super(index, name, serialType);
+ }
+}
diff --git a/qd-core/src/main/java/com/devexperts/qd/ng/RecordMapping.java b/qd-core/src/main/java/com/devexperts/qd/ng/RecordMapping.java
index 566709768..5622be80a 100644
--- a/qd-core/src/main/java/com/devexperts/qd/ng/RecordMapping.java
+++ b/qd-core/src/main/java/com/devexperts/qd/ng/RecordMapping.java
@@ -17,6 +17,7 @@
import com.devexperts.qd.SerialFieldType;
import com.devexperts.qd.kit.AbstractDataField;
import com.devexperts.qd.util.Decimal;
+import com.devexperts.util.TimeUtil;
import com.devexperts.util.WideDecimal;
import java.util.HashMap;
@@ -75,20 +76,28 @@ public final DataRecord getRecord() {
return record;
}
- protected final int getInt(RecordCursor cursor, int int_field_index) {
- return cursor.getIntMappedImpl(record, int_field_index);
+ protected final int getInt(RecordCursor cursor, int intFieldIndex) {
+ return cursor.getIntMappedImpl(record, intFieldIndex);
}
- protected final Object getObj(RecordCursor cursor, int obj_field_index) {
- return cursor.getObjMappedImpl(record, obj_field_index);
+ protected final long getLong(RecordCursor cursor, int intFieldIndex) {
+ return cursor.getLongMappedImpl(record, intFieldIndex);
}
- protected final void setInt(RecordCursor cursor, int int_field_index, int value) {
- cursor.setIntMappedImpl(record, int_field_index, value);
+ protected final Object getObj(RecordCursor cursor, int objFieldIndex) {
+ return cursor.getObjMappedImpl(record, objFieldIndex);
}
- protected final void setObj(RecordCursor cursor, int obj_field_index, Object value) {
- cursor.setObjMappedImpl(record, obj_field_index, value);
+ protected final void setInt(RecordCursor cursor, int intFieldIndex, int value) {
+ cursor.setIntMappedImpl(record, intFieldIndex, value);
+ }
+
+ protected final void setLong(RecordCursor cursor, int intFieldIndex, long value) {
+ cursor.setLongMappedImpl(record, intFieldIndex, value);
+ }
+
+ protected final void setObj(RecordCursor cursor, int objFieldIndex, Object value) {
+ cursor.setObjMappedImpl(record, objFieldIndex, value);
}
protected final int findIntField(String localName, boolean required) {
@@ -100,10 +109,14 @@ protected final int findIntField(String localName, boolean required) {
return Integer.MIN_VALUE;
}
+ // Integer, Long and Decimal field converters
+
protected final int getAsInt(RecordCursor cursor, int fieldId) {
switch (fieldId & SerialFieldType.Bits.REPRESENTATION_MASK) {
case SerialFieldType.Bits.FLAG_INT:
return cursor.getIntMappedImpl(record, fieldId >> 8);
+ case SerialFieldType.Bits.FLAG_LONG:
+ return (int) cursor.getLongMappedImpl(record, fieldId >> 8);
case SerialFieldType.Bits.FLAG_DECIMAL:
return (int) Decimal.toDouble(cursor.getIntMappedImpl(record, fieldId >> 8));
case SerialFieldType.Bits.FLAG_WIDE_DECIMAL:
@@ -117,6 +130,8 @@ protected final long getAsLong(RecordCursor cursor, int fieldId) {
switch (fieldId & SerialFieldType.Bits.REPRESENTATION_MASK) {
case SerialFieldType.Bits.FLAG_INT:
return cursor.getIntMappedImpl(record, fieldId >> 8);
+ case SerialFieldType.Bits.FLAG_LONG:
+ return cursor.getLongMappedImpl(record, fieldId >> 8);
case SerialFieldType.Bits.FLAG_DECIMAL:
return (long) Decimal.toDouble(cursor.getIntMappedImpl(record, fieldId >> 8));
case SerialFieldType.Bits.FLAG_WIDE_DECIMAL:
@@ -130,6 +145,8 @@ protected final double getAsDouble(RecordCursor cursor, int fieldId) {
switch (fieldId & SerialFieldType.Bits.REPRESENTATION_MASK) {
case SerialFieldType.Bits.FLAG_INT:
return cursor.getIntMappedImpl(record, fieldId >> 8);
+ case SerialFieldType.Bits.FLAG_LONG:
+ return cursor.getLongMappedImpl(record, fieldId >> 8);
case SerialFieldType.Bits.FLAG_DECIMAL:
return Decimal.toDouble(cursor.getIntMappedImpl(record, fieldId >> 8));
case SerialFieldType.Bits.FLAG_WIDE_DECIMAL:
@@ -143,6 +160,8 @@ protected final int getAsTinyDecimal(RecordCursor cursor, int fieldId) {
switch (fieldId & SerialFieldType.Bits.REPRESENTATION_MASK) {
case SerialFieldType.Bits.FLAG_INT:
return Decimal.composeDecimal(cursor.getIntMappedImpl(record, fieldId >> 8), 0);
+ case SerialFieldType.Bits.FLAG_LONG:
+ return Decimal.composeDecimal(cursor.getLongMappedImpl(record, fieldId >> 8), 0);
case SerialFieldType.Bits.FLAG_DECIMAL:
return cursor.getIntMappedImpl(record, fieldId >> 8);
case SerialFieldType.Bits.FLAG_WIDE_DECIMAL:
@@ -156,6 +175,8 @@ protected final long getAsWideDecimal(RecordCursor cursor, int fieldId) {
switch (fieldId & SerialFieldType.Bits.REPRESENTATION_MASK) {
case SerialFieldType.Bits.FLAG_INT:
return WideDecimal.composeWide(cursor.getIntMappedImpl(record, fieldId >> 8), 0);
+ case SerialFieldType.Bits.FLAG_LONG:
+ return WideDecimal.composeWide(cursor.getLongMappedImpl(record, fieldId >> 8), 0);
case SerialFieldType.Bits.FLAG_DECIMAL:
return Decimal.tinyToWide(cursor.getIntMappedImpl(record, fieldId >> 8));
case SerialFieldType.Bits.FLAG_WIDE_DECIMAL:
@@ -170,6 +191,9 @@ protected final void setAsInt(RecordCursor cursor, int fieldId, int value) {
case SerialFieldType.Bits.FLAG_INT:
cursor.setIntMappedImpl(record, fieldId >> 8, value);
break;
+ case SerialFieldType.Bits.FLAG_LONG:
+ cursor.setLongMappedImpl(record, fieldId >> 8, value);
+ break;
case SerialFieldType.Bits.FLAG_DECIMAL:
cursor.setIntMappedImpl(record, fieldId >> 8, Decimal.composeDecimal(value, 0));
break;
@@ -186,6 +210,9 @@ protected final void setAsLong(RecordCursor cursor, int fieldId, long value) {
case SerialFieldType.Bits.FLAG_INT:
cursor.setIntMappedImpl(record, fieldId >> 8, (int) value);
break;
+ case SerialFieldType.Bits.FLAG_LONG:
+ cursor.setLongMappedImpl(record, fieldId >> 8, value);
+ break;
case SerialFieldType.Bits.FLAG_DECIMAL:
cursor.setIntMappedImpl(record, fieldId >> 8, Decimal.composeDecimal(value, 0));
break;
@@ -202,6 +229,9 @@ protected final void setAsDouble(RecordCursor cursor, int fieldId, double value)
case SerialFieldType.Bits.FLAG_INT:
cursor.setIntMappedImpl(record, fieldId >> 8, (int) value);
break;
+ case SerialFieldType.Bits.FLAG_LONG:
+ cursor.setLongMappedImpl(record, fieldId >> 8, (long) value);
+ break;
case SerialFieldType.Bits.FLAG_DECIMAL:
cursor.setIntMappedImpl(record, fieldId >> 8, Decimal.compose(value));
break;
@@ -218,6 +248,9 @@ protected final void setAsTinyDecimal(RecordCursor cursor, int fieldId, int valu
case SerialFieldType.Bits.FLAG_INT:
cursor.setIntMappedImpl(record, fieldId >> 8, (int) Decimal.toDouble(value));
break;
+ case SerialFieldType.Bits.FLAG_LONG:
+ cursor.setLongMappedImpl(record, fieldId >> 8, (long) Decimal.toDouble(value));
+ break;
case SerialFieldType.Bits.FLAG_DECIMAL:
cursor.setIntMappedImpl(record, fieldId >> 8, value);
break;
@@ -234,6 +267,9 @@ protected final void setAsWideDecimal(RecordCursor cursor, int fieldId, long val
case SerialFieldType.Bits.FLAG_INT:
cursor.setIntMappedImpl(record, fieldId >> 8, (int) WideDecimal.toLong(value));
break;
+ case SerialFieldType.Bits.FLAG_LONG:
+ cursor.setLongMappedImpl(record, fieldId >> 8, WideDecimal.toLong(value));
+ break;
case SerialFieldType.Bits.FLAG_DECIMAL:
cursor.setIntMappedImpl(record, fieldId >> 8, Decimal.wideToTiny(value));
break;
@@ -244,4 +280,54 @@ protected final void setAsWideDecimal(RecordCursor cursor, int fieldId, long val
throw new IllegalArgumentException();
}
}
+
+ // Time field converters
+
+ protected final int getAsTimeSeconds(RecordCursor cursor, int fieldId) {
+ switch (fieldId & SerialFieldType.Bits.REPRESENTATION_MASK) {
+ case SerialFieldType.Bits.FLAG_TIME_SECONDS:
+ return cursor.getIntMappedImpl(record, fieldId >> 8);
+ case SerialFieldType.Bits.FLAG_TIME_MILLIS:
+ return TimeUtil.getSecondsFromTime(cursor.getLongMappedImpl(record, fieldId >> 8));
+ default:
+ throw new IllegalArgumentException();
+ }
+ }
+
+ protected final void setAsTimeSeconds(RecordCursor cursor, int fieldId, int value) {
+ switch (fieldId & SerialFieldType.Bits.REPRESENTATION_MASK) {
+ case SerialFieldType.Bits.FLAG_TIME_SECONDS:
+ cursor.setIntMappedImpl(record, fieldId >> 8, value);
+ break;
+ case SerialFieldType.Bits.FLAG_TIME_MILLIS:
+ cursor.setLongMappedImpl(record, fieldId >> 8, value * 1000L);
+ break;
+ default:
+ throw new IllegalArgumentException();
+ }
+ }
+
+ protected final long getAsTimeMillis(RecordCursor cursor, int fieldId) {
+ switch (fieldId & SerialFieldType.Bits.REPRESENTATION_MASK) {
+ case SerialFieldType.Bits.FLAG_TIME_SECONDS:
+ return cursor.getIntMappedImpl(record, fieldId >> 8) * 1000L;
+ case SerialFieldType.Bits.FLAG_TIME_MILLIS:
+ return cursor.getLongMappedImpl(record, fieldId >> 8);
+ default:
+ throw new IllegalArgumentException();
+ }
+ }
+
+ protected final void setAsTimeMillis(RecordCursor cursor, int fieldId, long value) {
+ switch (fieldId & SerialFieldType.Bits.REPRESENTATION_MASK) {
+ case SerialFieldType.Bits.FLAG_TIME_SECONDS:
+ cursor.setIntMappedImpl(record, fieldId >> 8, TimeUtil.getSecondsFromTime(value));
+ break;
+ case SerialFieldType.Bits.FLAG_TIME_MILLIS:
+ cursor.setLongMappedImpl(record, fieldId >> 8, value);
+ break;
+ default:
+ throw new IllegalArgumentException();
+ }
+ }
}
diff --git a/qd-core/src/main/java/com/devexperts/qd/qtp/BinaryRecordDesc.java b/qd-core/src/main/java/com/devexperts/qd/qtp/BinaryRecordDesc.java
index 050310e5c..3004db1bd 100644
--- a/qd-core/src/main/java/com/devexperts/qd/qtp/BinaryRecordDesc.java
+++ b/qd-core/src/main/java/com/devexperts/qd/qtp/BinaryRecordDesc.java
@@ -29,6 +29,7 @@
import com.devexperts.qd.ng.RecordCursor;
import com.devexperts.qd.util.Decimal;
import com.devexperts.qd.util.TimeSequenceUtil;
+import com.devexperts.util.TimeUtil;
import com.devexperts.util.WideDecimal;
import java.io.IOException;
@@ -36,8 +37,10 @@
import static com.devexperts.qd.SerialFieldType.Bits.FLAG_DECIMAL;
import static com.devexperts.qd.SerialFieldType.Bits.FLAG_INT;
+import static com.devexperts.qd.SerialFieldType.Bits.FLAG_LONG;
import static com.devexperts.qd.SerialFieldType.Bits.FLAG_SEQUENCE;
-import static com.devexperts.qd.SerialFieldType.Bits.FLAG_TIME;
+import static com.devexperts.qd.SerialFieldType.Bits.FLAG_TIME_MILLIS;
+import static com.devexperts.qd.SerialFieldType.Bits.FLAG_TIME_SECONDS;
import static com.devexperts.qd.SerialFieldType.Bits.FLAG_WIDE_DECIMAL;
import static com.devexperts.qd.SerialFieldType.Bits.ID_BYTE;
import static com.devexperts.qd.SerialFieldType.Bits.ID_BYTE_ARRAY;
@@ -54,19 +57,18 @@ public class BinaryRecordDesc {
/*
* DESC integer bits layout
*
- * 4 bits 4 bits 24 bits
+ * 6 bits 4 bits 22 bits
* +-------+-------+-----------------+
* | FLD | SER | index |
* +-------+-------+-----------------+
- * 32 28 24 ..... 0
+ * 32 26 22 ... 0
*
* FLD -- defines the general operation and flips between fast/slow path
* SER -- defines serialization format in stream.
* index -- the index of this field in record.
- *
*/
- protected static final int SER_SHIFT = 24;
+ protected static final int SER_SHIFT = 22;
protected static final int SER_MASK = 0xf; // after shift
protected static final int SER_OTHER = 0; // never appears in desc array, used for special DESC_VOID and DESC_INVALID
@@ -86,25 +88,38 @@ public class BinaryRecordDesc {
protected static final int SER_MARSHALLED = 9;
protected static final int SER_PLAIN_OBJECT = 10; // deprecated, but still supported
- protected static final int FLD_SHIFT = 28;
+ protected static final int FLD_SHIFT = 26;
protected static final int FLD_SKIP = 0; // just skip the field that was read (never used for Write)
protected static final int FLD_INT = 1; // regular int field (w/o conversion)
protected static final int FLD_LONG = 2; // regular long field (w/o conversion)
protected static final int FLD_OBJ = 3; // regular obj field
+ // Int
protected static final int FLD_DECIMAL_TO_INT = 4; // conversion decimal->int
protected static final int FLD_INT_TO_DECIMAL = 5; // conversion int->decimal
protected static final int FLD_WIDE_DECIMAL_TO_INT = 6; // conversion WideDecimal->int
protected static final int FLD_INT_TO_WIDE_DECIMAL = 7; // conversion int->WideDecimal
+ // Decimal
protected static final int FLD_WIDE_DECIMAL_TO_DECIMAL = 8; // conversion WideDecimal->decimal
protected static final int FLD_DECIMAL_TO_WIDE_DECIMAL = 9; // conversion decimal->WideDecimal
protected static final int FLD_DECIMAL_TO_SHARES = 10; // conversion decimal->decimal shares (/1000)
protected static final int FLD_SHARES_TO_DECIMAL = 11; // conversion decimal shares->decimal (*1000)
protected static final int FLD_WIDE_DECIMAL_TO_SHARES = 12; // conversion WideDecimal->decimal shares (/1000)
protected static final int FLD_SHARES_TO_WIDE_DECIMAL = 13; // conversion decimal shares->WideDecimal (*1000)
+ // Special
protected static final int FLD_EVENT_TIME = 14; // RecordCursor.get/setEventTime (does not have index)
protected static final int FLD_EVENT_SEQUENCE = 15; // RecordCursor.get/setEventSequence (does not have index)
- // Note: FLD_VALUE of 16 can't be used unless FLD_SHIFT is changed
+ // Long
+ protected static final int FLD_DECIMAL_TO_LONG = 16;
+ protected static final int FLD_LONG_TO_DECIMAL = 17;
+ protected static final int FLD_WIDE_DECIMAL_TO_LONG = 18;
+ protected static final int FLD_LONG_TO_WIDE_DECIMAL = 19;
+ // Time
+ protected static final int FLD_TIME_MILLIS_TO_TIME_SECONDS = 20;
+ protected static final int FLD_TIME_SECONDS_TO_TIME_MILLIS = 21;
+ protected static final int FLD_TIME_MILLIS_TO_EVENT_TIME_SEQUENCE = 22; //TODO
+ protected static final int FLD_EVENT_TIME_SEQUENCE_TO_TIME_MILLIS = 23; //TODO
+ // Note: FLD_VALUE of 64 can't be used unless FLD_SHIFT is changed
protected static final int FLAG_SHARES = -1; // denotes decimal shares which are expressed in thousands
@@ -156,7 +171,7 @@ protected BinaryRecordDesc(DataRecord record, boolean eventTimeSequence, int dir
int nDesc = 0;
if (eventTimeSequence) {
names[nDesc] = BuiltinFields.EVENT_TIME_FIELD_NAME;
- types[nDesc] = SerialFieldType.TIME.getId();
+ types[nDesc] = SerialFieldType.TIME_SECONDS.getId();
descs[nDesc++] = DESC_EVENT_TIME;
names[nDesc] = BuiltinFields.EVENT_SEQUENCE_FIELD_NAME;
types[nDesc] = SerialFieldType.SEQUENCE.getId();
@@ -216,7 +231,7 @@ protected BinaryRecordDesc(DataRecord record, int nFld, String[] namesIn, int[]
int i = 0;
if (eventTimeSequence && nFld >= 1 &&
names[0].equals(BuiltinFields.EVENT_TIME_FIELD_NAME) &&
- types[0] == (ID_COMPACT_INT | FLAG_TIME))
+ types[0] == (ID_COMPACT_INT | FLAG_TIME_SECONDS))
{
i++;
descs[nDesc++] = DESC_EVENT_TIME;
@@ -377,12 +392,14 @@ protected void readFields(BufferedInput msg, RecordCursor cur, int nDesc) throws
setIntValue(cur, d & INDEX_MASK, (int) Decimal.toDouble((int) iVal), msg);
break;
case FLD_INT_TO_DECIMAL:
+ case FLD_LONG_TO_DECIMAL:
setIntValue(cur, d & INDEX_MASK, Decimal.composeDecimal(iVal, 0), msg);
break;
case FLD_WIDE_DECIMAL_TO_INT:
setIntValue(cur, d & INDEX_MASK, (int) WideDecimal.toLong(iVal), msg);
break;
case FLD_INT_TO_WIDE_DECIMAL:
+ case FLD_LONG_TO_WIDE_DECIMAL:
setLongValue(cur, d & INDEX_MASK, WideDecimal.composeWide(iVal, 0), msg);
break;
case FLD_WIDE_DECIMAL_TO_DECIMAL:
@@ -393,7 +410,6 @@ protected void readFields(BufferedInput msg, RecordCursor cur, int nDesc) throws
break;
case FLD_SHARES_TO_DECIMAL:
setIntValue(cur, d & INDEX_MASK, Decimal.compose(Decimal.toDouble((int) iVal) * 1000.0), msg);
- iVal = Decimal.compose(Decimal.toDouble(cur.getInt(d & INDEX_MASK)) / 1000.0);
break;
case FLD_SHARES_TO_WIDE_DECIMAL:
setLongValue(cur, d & INDEX_MASK, WideDecimal.composeWide(Decimal.toDouble((int) iVal) * 1000.0), msg);
@@ -404,6 +420,18 @@ protected void readFields(BufferedInput msg, RecordCursor cur, int nDesc) throws
case FLD_EVENT_SEQUENCE:
cur.setEventSequence((int) iVal);
break;
+ case FLD_DECIMAL_TO_LONG:
+ setLongValue(cur, d & INDEX_MASK, (long) Decimal.toDouble((int) iVal), msg);
+ break;
+ case FLD_WIDE_DECIMAL_TO_LONG:
+ setLongValue(cur, d & INDEX_MASK, WideDecimal.toLong(iVal), msg);
+ break;
+ case FLD_TIME_MILLIS_TO_TIME_SECONDS:
+ setIntValue(cur, d & INDEX_MASK, TimeUtil.getSecondsFromTime(iVal), msg);
+ break;
+ case FLD_TIME_SECONDS_TO_TIME_MILLIS:
+ setLongValue(cur, d & INDEX_MASK, iVal * 1000L, msg);
+ break;
default:
throw new AssertionError();
}
@@ -459,12 +487,14 @@ private void writeFields(BufferedOutput msg, RecordCursor cur, int nDesc, long e
oVal = cur.getObj(d & INDEX_MASK);
break;
case FLD_DECIMAL_TO_INT:
+ case FLD_DECIMAL_TO_LONG:
iVal = (long) Decimal.toDouble(cur.getInt(d & INDEX_MASK));
break;
case FLD_INT_TO_DECIMAL:
iVal = Decimal.composeDecimal(cur.getInt(d & INDEX_MASK), 0);
break;
case FLD_WIDE_DECIMAL_TO_INT:
+ case FLD_WIDE_DECIMAL_TO_LONG:
iVal = WideDecimal.toLong(cur.getLong(d & INDEX_MASK));
break;
case FLD_INT_TO_WIDE_DECIMAL:
@@ -488,6 +518,18 @@ private void writeFields(BufferedOutput msg, RecordCursor cur, int nDesc, long e
case FLD_EVENT_SEQUENCE:
iVal = TimeSequenceUtil.getSequenceFromTimeSequence(eventTimeSequence);
break;
+ case FLD_LONG_TO_DECIMAL:
+ iVal = Decimal.composeDecimal(cur.getLong(d & INDEX_MASK), 0);
+ break;
+ case FLD_LONG_TO_WIDE_DECIMAL:
+ iVal = WideDecimal.composeWide(cur.getLong(d & INDEX_MASK), 0);
+ break;
+ case FLD_TIME_MILLIS_TO_TIME_SECONDS:
+ iVal = TimeUtil.getSecondsFromTime(cur.getLong(d & INDEX_MASK));
+ break;
+ case FLD_TIME_SECONDS_TO_TIME_MILLIS:
+ iVal = cur.getInt(d & INDEX_MASK) * 1000L;
+ break;
default:
throw new AssertionError();
}
@@ -621,8 +663,13 @@ private static int field2Desc(String name, int type, DataField f, int dir, boole
}
private static int getIntConverterType(int from, int to) {
+ // Most fields will not need conversion
if (from == to)
return FLD_INT;
+ // Specify conversion
+ //TODO
+ // This code is repetitive and error-prone
+ // Refactor into either sparse matrix or (from+to) encoded integer
if (from == FLAG_DECIMAL && to == FLAG_INT)
return FLD_DECIMAL_TO_INT;
if (from == FLAG_INT && to == FLAG_DECIMAL)
@@ -643,6 +690,18 @@ private static int getIntConverterType(int from, int to) {
return FLD_WIDE_DECIMAL_TO_SHARES;
if (from == FLAG_SHARES && to == FLAG_WIDE_DECIMAL)
return FLD_SHARES_TO_WIDE_DECIMAL;
+ if (from == FLAG_DECIMAL && to == FLAG_LONG)
+ return FLD_DECIMAL_TO_LONG;
+ if (from == FLAG_LONG && to == FLAG_DECIMAL)
+ return FLD_LONG_TO_DECIMAL;
+ if (from == FLAG_WIDE_DECIMAL && to == FLAG_LONG)
+ return FLD_WIDE_DECIMAL_TO_LONG;
+ if (from == FLAG_LONG && to == FLAG_WIDE_DECIMAL)
+ return FLD_LONG_TO_WIDE_DECIMAL;
+ if (from == FLAG_TIME_MILLIS && to == FLAG_TIME_SECONDS)
+ return FLD_TIME_MILLIS_TO_TIME_SECONDS;
+ if (from == FLAG_TIME_SECONDS && to == FLAG_TIME_MILLIS)
+ return FLD_TIME_SECONDS_TO_TIME_MILLIS;
return FLD_SKIP;
}
}
diff --git a/qd-core/src/main/java/com/devexperts/qd/qtp/ConnectionQTPComposer.java b/qd-core/src/main/java/com/devexperts/qd/qtp/ConnectionQTPComposer.java
index 6c4ba1441..2e39464da 100644
--- a/qd-core/src/main/java/com/devexperts/qd/qtp/ConnectionQTPComposer.java
+++ b/qd-core/src/main/java/com/devexperts/qd/qtp/ConnectionQTPComposer.java
@@ -125,6 +125,7 @@ protected BinaryRecordDesc getRequestedRecordDesc(DataRecord record) {
}
boolean wideDecimalSupported = true;
+ //FIXME preciseTimeSupported
@Override
protected boolean isWideDecimalSupported() {
diff --git a/qd-core/src/test/java/com/devexperts/qd/test/HistorySnapshotMTStressTest.java b/qd-core/src/test/java/com/devexperts/qd/impl/matrix/HistorySnapshotMTStressTest.java
similarity index 92%
rename from qd-core/src/test/java/com/devexperts/qd/test/HistorySnapshotMTStressTest.java
rename to qd-core/src/test/java/com/devexperts/qd/impl/matrix/HistorySnapshotMTStressTest.java
index 98a07edb2..951def028 100644
--- a/qd-core/src/test/java/com/devexperts/qd/test/HistorySnapshotMTStressTest.java
+++ b/qd-core/src/test/java/com/devexperts/qd/impl/matrix/HistorySnapshotMTStressTest.java
@@ -9,7 +9,7 @@
* http://mozilla.org/MPL/2.0/.
* !__
*/
-package com.devexperts.qd.test;
+package com.devexperts.qd.impl.matrix;
import com.devexperts.logging.Logging;
import com.devexperts.logging.TraceLogging;
@@ -21,11 +21,11 @@
import com.devexperts.qd.QDAgent;
import com.devexperts.qd.QDDistributor;
import com.devexperts.qd.QDFactory;
-import com.devexperts.qd.QDHistory;
import com.devexperts.qd.kit.CompactIntField;
import com.devexperts.qd.kit.DefaultRecord;
import com.devexperts.qd.kit.DefaultScheme;
import com.devexperts.qd.kit.PentaCodec;
+import com.devexperts.qd.kit.RecordOnlyFilter;
import com.devexperts.qd.ng.AbstractRecordSink;
import com.devexperts.qd.ng.EventFlag;
import com.devexperts.qd.ng.RecordBuffer;
@@ -38,9 +38,13 @@
import com.devexperts.qd.qtp.MessageType;
import com.devexperts.qd.stats.QDStats;
import junit.framework.Assert;
-import junit.framework.TestCase;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -55,7 +59,12 @@
import java.util.concurrent.locks.LockSupport;
import javax.annotation.Nonnull;
-public class HistorySnapshotMTStressTest extends TestCase {
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+@RunWith(Parameterized.class)
+public class HistorySnapshotMTStressTest {
private static final Logging log = Logging.getLogging(HistorySnapshotMTStressTest.class);
private static final int N_SECS = 5; // "production" mode -- just run for 5 seconds
@@ -89,26 +98,57 @@ public class HistorySnapshotMTStressTest extends TestCase {
private static final PentaCodec CODEC = PentaCodec.INSTANCE;
private static final DataScheme SCHEME = new DefaultScheme(CODEC, RECORD);
+ private final boolean unconflated;
+
private volatile boolean stopped;
- private final HSF hsf = new HSF();
- private final QDHistory history = QDFactory.getDefaultFactory().createHistory(SCHEME, QDStats.VOID, hsf);
- private final DistributorThread distributorThread = new DistributorThread(history.distributorBuilder().build());
- private final String[] symbols = new String[N_SYMBOLS]; // all have zero ciphers
+ private DistributorThread distributorThread;
+ private String[] symbols; // all have zero ciphers
+
+ private volatile BlockingQueue uncaughtException;
+ private List stackTraces;
- private volatile BlockingQueue uncaughtException = new ArrayBlockingQueue<>(1);
- private final List stackTraces = new ArrayList<>();
+ private List threads;
+ private List agents;
- private final List threads = new ArrayList<>();
- private final List agents = new ArrayList<>();
+ @Parameterized.Parameters(name = "unconflated={0}")
+ public static Iterable data() {
+ return Arrays.asList(new Object[][] {
+ { false },
+ { true },
+ });
+ }
+
+ public HistorySnapshotMTStressTest(boolean unconflated) {
+ this.unconflated = unconflated;
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ History history = new History(QDFactory.getDefaultFactory().historyBuilder()
+ .withScheme(SCHEME).withStats(QDStats.VOID).withHistoryFilter(new HSF()),
+ new RecordOnlyFilter(SCHEME) {
+ @Override
+ public boolean acceptRecord(DataRecord record) {
+ return !unconflated;
+ }
+ });
+ distributorThread = new DistributorThread(history.distributorBuilder().build());
+ stopped = false;
- @Override
- protected void setUp() throws Exception {
TraceLogging.restart();
+ symbols = new String[N_SYMBOLS];
for (int i = 0; i < N_SYMBOLS; i++) {
symbols[i] = "SYMBOL_" + i;
- Assert.assertEquals(0, CODEC.encode(symbols[i]));
+ assertEquals(0, CODEC.encode(symbols[i]));
}
+
+ uncaughtException = new ArrayBlockingQueue<>(1);
+ stackTraces = new ArrayList<>();
+
+ threads = new ArrayList<>();
+ agents = new ArrayList<>();
+
threads.add(distributorThread);
for (int i = 0; i < N_AGENTS; i++) {
AgentThread agent = new AgentThread(i, history.agentBuilder()
@@ -124,6 +164,7 @@ protected void setUp() throws Exception {
}
}
+ @Test
public void testStress() throws InterruptedException {
for (Thread thread : threads) {
thread.start();
diff --git a/qd-core/src/test/java/com/devexperts/qd/impl/matrix/HistoryTxBlockingTest.java b/qd-core/src/test/java/com/devexperts/qd/impl/matrix/HistoryTxBlockingTest.java
deleted file mode 100644
index 808291f09..000000000
--- a/qd-core/src/test/java/com/devexperts/qd/impl/matrix/HistoryTxBlockingTest.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * !++
- * QDS - Quick Data Signalling Library
- * !-
- * Copyright (C) 2002 - 2020 Devexperts LLC
- * !-
- * 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/.
- * !__
- */
-package com.devexperts.qd.impl.matrix;
-
-import com.devexperts.qd.QDAgent;
-import com.devexperts.qd.ng.AbstractRecordProvider;
-import com.devexperts.qd.ng.AbstractRecordSink;
-import com.devexperts.qd.ng.RecordBuffer;
-import com.devexperts.qd.ng.RecordCursor;
-import com.devexperts.qd.ng.RecordListener;
-import com.devexperts.qd.ng.RecordMode;
-import com.devexperts.qd.ng.RecordProvider;
-import com.devexperts.qd.ng.RecordSink;
-
-/**
- * The same test suite as in {@link HistoryTxTest}, but buffer size is limited to 1
- * and blocking is configured, while the actual buffer is off-loaded to a separate buffer.
- * This way buffer blocking in History is stress-tested.
- */
-public class HistoryTxBlockingTest extends HistoryTxTest {
- private RecordBuffer buf;
- private RecordListener delegateListener;
- private AbstractRecordProvider provider;
-
- public HistoryTxBlockingTest() {
- blocking = true;
- }
-
- @Override
- RecordProvider getProvider(final QDAgent agent) {
- agent.setBufferOverflowStrategy(QDAgent.BufferOverflowStrategy.BLOCK);
- agent.setMaxBufferSize(1);
- // our buffer
- buf = RecordBuffer.getInstance(agent.getMode());
- // our provider
- provider = new AbstractRecordProvider() {
- @Override
- public RecordMode getMode() {
- return agent.getMode();
- }
-
- @Override
- public boolean retrieve(RecordSink sink) {
- return buf.retrieve(sink);
- }
-
- @Override
- public void setRecordListener(RecordListener listener) {
- delegateListener = listener;
- if (listener != null && buf.hasNext())
- listener.recordsAvailable(provider);
- }
- };
- // will mimic conflation logic
- final RecordSink conflatingSink = new AbstractRecordSink() {
- long lastPosition = -1;
- @Override
- public void append(RecordCursor cursor) {
- if (lastPosition >= buf.getPosition()) {
- RecordCursor writeCursor = buf.writeCursorAt(lastPosition);
- if (writeCursor.getTime() == cursor.getTime()) {
- // conflate
- writeCursor.setEventFlags(cursor.getEventFlags());
- writeCursor.copyDataFrom(cursor);
- return;
- }
- }
- lastPosition = buf.getLimit();
- buf.append(cursor);
- }
- };
- // install agent's listener
- agent.setRecordListener(p -> {
- boolean wasEmpty = !buf.hasNext();
- agent.retrieve(conflatingSink);
- if (wasEmpty && buf.hasNext() && delegateListener != null)
- delegateListener.recordsAvailable(provider);
- });
- return provider;
- }
-
- @Override
- void closeAgent() {
- super.closeAgent();
- delegateListener = null;
- }
-}
diff --git a/qd-core/src/test/java/com/devexperts/qd/impl/matrix/HistoryTxTest.java b/qd-core/src/test/java/com/devexperts/qd/impl/matrix/HistoryTxTest.java
index b7993123e..d5aa7c2e8 100644
--- a/qd-core/src/test/java/com/devexperts/qd/impl/matrix/HistoryTxTest.java
+++ b/qd-core/src/test/java/com/devexperts/qd/impl/matrix/HistoryTxTest.java
@@ -27,20 +27,29 @@
import com.devexperts.qd.kit.DefaultRecord;
import com.devexperts.qd.kit.DefaultScheme;
import com.devexperts.qd.kit.PentaCodec;
+import com.devexperts.qd.kit.RecordOnlyFilter;
+import com.devexperts.qd.ng.AbstractRecordProvider;
import com.devexperts.qd.ng.AbstractRecordSink;
import com.devexperts.qd.ng.EventFlag;
import com.devexperts.qd.ng.RecordBuffer;
import com.devexperts.qd.ng.RecordCursor;
+import com.devexperts.qd.ng.RecordListener;
import com.devexperts.qd.ng.RecordMode;
import com.devexperts.qd.ng.RecordProvider;
+import com.devexperts.qd.ng.RecordSink;
import com.devexperts.qd.stats.QDStats;
-import com.devexperts.test.TraceRunner;
+import com.devexperts.test.TraceRunnerWithParametersFactory;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -48,7 +57,8 @@
* A suite of small single-agent single-distributor tests of {@link QDHistory} snapshot,
* update, and transaction logic.
*/
-@RunWith(TraceRunner.class)
+@RunWith(Parameterized.class)
+@Parameterized.UseParametersRunnerFactory(TraceRunnerWithParametersFactory.class)
public class HistoryTxTest {
private static final int TX_PENDING = EventFlag.TX_PENDING.flag();
private static final int REMOVE_EVENT = EventFlag.REMOVE_EVENT.flag();
@@ -68,25 +78,46 @@ public class HistoryTxTest {
private static final int CIPHER = CODEC.encode("TST");
private static final DataScheme SCHEME = new DefaultScheme(CODEC, RECORD);
- private final HistoryImpl history = new HistoryImpl(QDFactory.getDefaultFactory().historyBuilder()
- .withScheme(SCHEME)
- .withStats(QDStats.VOID)
- .withHistoryFilter(new HSF()));
+ @Parameterized.Parameters(name= "blocking={0}, unconflated={1}")
+ public static Iterable data() {
+ return Arrays.asList(new Object[][] {
+ { false, false }, { true, false },
+ { false, true }, { true, true },
+ });
+ }
+
+ private final boolean blocking;
+ private final boolean unconflated;
- private final QDDistributor distributor = history.distributorBuilder().build();
+ private HistoryImpl history;
+ private QDDistributor distributor;
private QDAgent agent;
private QDAgent agent2;
private boolean available;
- RecordProvider provider; // HistoryTxBlockingTest overrides it
+ RecordProvider provider;
+ // Blocking mode related fields
+ RecordProvider blockingProvider;
+ RecordListener blockingListener;
+
RecordBuffer retrieveBuf = new RecordBuffer(RecordMode.FLAGGED_DATA);
RecordBuffer distributeBuf = new RecordBuffer(RecordMode.FLAGGED_DATA);
boolean distributeBatch;
- boolean blocking; // HistoryTxBlockingTest sets to true
Runnable betweenProcessPhases;
+ public HistoryTxTest(boolean blocking, boolean unconflated) {
+ this.blocking = blocking;
+ this.unconflated = unconflated;
+ }
+
@Before
public void setUp() throws Exception {
+ history = new HistoryImpl(QDFactory.getDefaultFactory().historyBuilder()
+ .withScheme(SCHEME)
+ .withStats(QDStats.VOID)
+ .withHistoryFilter(new HSF()));
+ distributor = history.distributorBuilder().build();
+
history.setErrorHandler(new QDErrorHandler() {
@Override
public void handleDataError(DataProvider provider, Throwable t) {
@@ -118,7 +149,7 @@ public void testLegacyToSnapshotUpdate() {
// ---
// History snapshot supporting agent just receives fresh snapshot and also removes legacy data.
if (blocking) {
- // in blocking mode all them are TX_PENDING (starts retrieval from non-complete HB)
+ // in blocking mode all of them are TX_PENDING (starts retrieval from non-complete HB)
expectMore(3, 13, TX_PENDING | SNAPSHOT_BEGIN); // now there is a snapshot(!)
expectMore(1, 14, TX_PENDING);
expectJust(0, 0, SNAPSHOT_END | REMOVE_EVENT);
@@ -316,7 +347,11 @@ public void testSimpleSnapshotUpdate() {
// update snapshot
distribute(4, 13, SNAPSHOT_BEGIN);
// ---
- expectJust(4, 13, TX_PENDING);
+ if (unconflated) {
+ expectJust(4, 13, TX_PENDING | SNAPSHOT_BEGIN);
+ } else {
+ expectJust(4, 13, TX_PENDING);
+ }
// ---
distribute(3, 14, 0);
// ---
@@ -325,7 +360,12 @@ public void testSimpleSnapshotUpdate() {
distribute(2, 11, 0);
distribute(1, 12, 0);
// ---
- expectNothing();
+ if (unconflated) {
+ expectMore(2, 11, TX_PENDING);
+ expectJust(1, 12, TX_PENDING);
+ } else {
+ expectNothing();
+ }
// ---
distribute(0, 0, SNAPSHOT_END | REMOVE_EVENT);
expectJust(0, 0, SNAPSHOT_END | REMOVE_EVENT); // gets augmented with snapshot end (it goes to the sub time)
@@ -350,10 +390,15 @@ public void testSnapshotSweepRemoveClean() {
distribute(1, 13, SNAPSHOT_BEGIN);
distribute(0, 0, REMOVE_EVENT | SNAPSHOT_END);
// ---
- expectMore(4, 0, REMOVE_EVENT | TX_PENDING);
- expectMore(3, 0, REMOVE_EVENT | TX_PENDING);
- expectMore(2, 0, REMOVE_EVENT | TX_PENDING);
- expectJust(0, 0, REMOVE_EVENT);
+ if (unconflated) {
+ expectMore(1, 13, (blocking ? TX_PENDING : 0) | SNAPSHOT_BEGIN);
+ expectJust(0, 0, REMOVE_EVENT | SNAPSHOT_END);
+ } else {
+ expectMore(4, 0, REMOVE_EVENT | TX_PENDING);
+ expectMore(3, 0, REMOVE_EVENT | TX_PENDING);
+ expectMore(2, 0, REMOVE_EVENT | TX_PENDING);
+ expectJust(0, 0, REMOVE_EVENT);
+ }
}
@Test
@@ -403,12 +448,20 @@ public void testSnapshotSweepRemoveDirty() {
// send snapshot with only 1 items left (confirm it), but don't end it
distribute(1, 13, SNAPSHOT_BEGIN);
// ---
- expectMore(4, 0, REMOVE_EVENT | TX_PENDING);
- expectMore(3, 0, REMOVE_EVENT | TX_PENDING);
- expectJust(2, 0, REMOVE_EVENT | TX_PENDING);
+ if (unconflated) {
+ expectJust(1, 13, TX_PENDING | SNAPSHOT_BEGIN);
+ } else {
+ expectMore(4, 0, REMOVE_EVENT | TX_PENDING);
+ expectMore(3, 0, REMOVE_EVENT | TX_PENDING);
+ expectJust(2, 0, REMOVE_EVENT | TX_PENDING);
+ }
// ---
distribute(0, 0, REMOVE_EVENT | SNAPSHOT_END);
- expectJust(0, 0, REMOVE_EVENT);
+ if (unconflated) {
+ expectJust(0, 0, REMOVE_EVENT | SNAPSHOT_END);
+ } else {
+ expectJust(0, 0, REMOVE_EVENT);
+ }
}
@Test
@@ -453,12 +506,16 @@ public void testSnapshotSweepRemovePartSub1() {
// send snapshot with only 1 items left (confirm it), but outside of sub (!)
distribute(1, 13, SNAPSHOT_BEGIN); // also implicit snapshot end for timeSub=2
// ---
- expectMore(4, 0, REMOVE_EVENT | TX_PENDING);
- expectMore(3, 0, REMOVE_EVENT | TX_PENDING);
- if (blocking) {
+ if (unconflated) {
+ expectJust(2, 0, REMOVE_EVENT | SNAPSHOT_BEGIN | SNAPSHOT_END);
+ } else if (blocking) {
+ expectMore(4, 0, REMOVE_EVENT | TX_PENDING);
+ expectMore(3, 0, REMOVE_EVENT | TX_PENDING);
expectMore(2, 0, REMOVE_EVENT | TX_PENDING);
expectJust(Long.MAX_VALUE, 0, REMOVE_EVENT);
} else {
+ expectMore(4, 0, REMOVE_EVENT | TX_PENDING);
+ expectMore(3, 0, REMOVE_EVENT | TX_PENDING);
expectJust(2, 0, REMOVE_EVENT);
}
// ---
@@ -481,11 +538,14 @@ public void testSnapshotSweepRemovePartSub2() {
// send snapshot with only 1 items left (confirm it), but outside of sub (!)
distribute(1, 13, SNAPSHOT_BEGIN);
// ---
- expectMore(4, 0, REMOVE_EVENT | TX_PENDING);
- if (blocking) {
+ if (unconflated) {
+ expectJust(2, 0, REMOVE_EVENT | SNAPSHOT_BEGIN | SNAPSHOT_END);
+ } else if (blocking) {
+ expectMore(4, 0, REMOVE_EVENT | TX_PENDING);
expectMore(3, 0, REMOVE_EVENT | TX_PENDING);
expectJust(Long.MAX_VALUE, 0, REMOVE_EVENT);
} else {
+ expectMore(4, 0, REMOVE_EVENT | TX_PENDING);
expectJust(3, 0, REMOVE_EVENT); // end if TX was optimized to the last buffered event
}
// ---
@@ -511,13 +571,21 @@ public void testSnapshotSweepRemoveUpdate() {
// send snapshot with only 1 items left (update it!)
distribute(1, 15, SNAPSHOT_BEGIN);
// ---
- expectMore(4, 0, REMOVE_EVENT | TX_PENDING);
- expectMore(3, 0, REMOVE_EVENT | TX_PENDING);
- expectMore(2, 0, REMOVE_EVENT | TX_PENDING);
- expectJust(1, 15, TX_PENDING);
+ if (unconflated) {
+ expectJust(1, 15, SNAPSHOT_BEGIN | TX_PENDING);
+ } else {
+ expectMore(4, 0, REMOVE_EVENT | TX_PENDING);
+ expectMore(3, 0, REMOVE_EVENT | TX_PENDING);
+ expectMore(2, 0, REMOVE_EVENT | TX_PENDING);
+ expectJust(1, 15, TX_PENDING);
+ }
// ---
distribute(0, 0, REMOVE_EVENT | SNAPSHOT_END);
- expectJust(0, 0, REMOVE_EVENT);
+ if (unconflated) {
+ expectJust(0, 0, SNAPSHOT_END | REMOVE_EVENT);
+ } else {
+ expectJust(0, 0, REMOVE_EVENT);
+ }
}
@Test
@@ -536,12 +604,16 @@ public void testSnapshotSweepRemoveUpdatePartSub() {
// send snapshot with only 1 items left (update it!)
distribute(1, 15, SNAPSHOT_BEGIN);
// ---
- expectMore(4, 0, REMOVE_EVENT | TX_PENDING);
- expectMore(3, 0, REMOVE_EVENT | TX_PENDING);
- if (blocking) {
+ if (unconflated) {
+ expectJust(2, 0, REMOVE_EVENT | SNAPSHOT_BEGIN | SNAPSHOT_END);
+ } else if (blocking) {
+ expectMore(4, 0, REMOVE_EVENT | TX_PENDING);
+ expectMore(3, 0, REMOVE_EVENT | TX_PENDING);
expectMore(2, 0, REMOVE_EVENT | TX_PENDING);
expectJust(Long.MAX_VALUE, 0, REMOVE_EVENT);
} else {
+ expectMore(4, 0, REMOVE_EVENT | TX_PENDING);
+ expectMore(3, 0, REMOVE_EVENT | TX_PENDING);
expectJust(2, 0, REMOVE_EVENT);
}
// ---
@@ -553,26 +625,40 @@ public void testSnapshotSweepRemoveUpdatePartSub() {
public void testSnapshotInsert() {
createAgent(0, true);
// send snapshot (3 even items)
- distribute(4, 10, SNAPSHOT_BEGIN);
- distribute(2, 11, 0);
- distribute(0, 12, SNAPSHOT_END);
+ distribute(6, 10, SNAPSHOT_BEGIN);
+ distribute(4, 11, 0);
+ distribute(2, 12, 0);
+ distribute(0, 13, SNAPSHOT_END);
// ---
- expectMore(4, 10, SNAPSHOT_BEGIN);
- expectMore(2, 11, 0);
- expectJust(0, 12, SNAPSHOT_END);
+ expectMore(6, 10, SNAPSHOT_BEGIN);
+ expectMore(4, 11, 0);
+ expectMore(2, 12, 0);
+ expectJust(0, 13, SNAPSHOT_END);
// send snapshot with 5 items (add two more)
- distribute(4, 10, SNAPSHOT_BEGIN);
- distribute(3, 13, 0);
- distribute(2, 11, 0);
- distribute(1, 14, 0);
- distribute(0, 12, SNAPSHOT_END);
+ distribute(6, 10, SNAPSHOT_BEGIN);
+ distribute(4, 11, 0);
+ distribute(3, 14, 0);
+ distribute(2, 12, 0);
+ distribute(1, 15, 0);
+ distribute(0, 13, SNAPSHOT_END);
// ---
- expectMore(3, 13, TX_PENDING);
- if (blocking) { // cannot optimize tx end in blocking mode (just one record in buffer)
- expectMore(1, 14, TX_PENDING); // still pending
- expectJust(0, 12, 0); // explicit TX_END
- } else
- expectJust(1, 14, 0); // optimized end of transaction
+ if (unconflated) {
+ // Snapshot repeats previously retrieved data
+ expectMore(6, 10, SNAPSHOT_BEGIN);
+ expectMore(4, 11, 0);
+ // In blocking mode updated event cause TX_PENDING flag to be set
+ expectMore(3, 14, blocking ? TX_PENDING : 0);
+ expectMore(2, 12, blocking ? TX_PENDING : 0);
+ expectMore(1, 15, blocking ? TX_PENDING : 0);
+ expectJust(0, 13, SNAPSHOT_END); // explicit TX_END
+ } else if (blocking) { // cannot optimize tx end in blocking mode (just one record in buffer)
+ expectMore(3, 14, TX_PENDING);
+ expectMore(1, 15, TX_PENDING); // still pending
+ expectJust(0, 13, 0); // explicit TX_END
+ } else {
+ expectMore(3, 14, TX_PENDING);
+ expectJust(1, 15, 0); // optimized end of transaction
+ }
}
@Test
@@ -585,6 +671,7 @@ public void testSnapshotInsertPartSub1() {
// ---
expectMore(4, 10, SNAPSHOT_BEGIN);
expectJust(2, 11, SNAPSHOT_END);
+
// send snapshot with 5 items (add two more)
distribute(4, 10, SNAPSHOT_BEGIN);
distribute(3, 13, 0);
@@ -592,11 +679,16 @@ public void testSnapshotInsertPartSub1() {
distribute(1, 14, 0);
distribute(0, 12, SNAPSHOT_END);
// ---
- if (blocking) { // cannot optimize tx end in blocking mode (just one record in buffer)
+ if (unconflated) {
+ expectMore(4, 10, SNAPSHOT_BEGIN);
+ expectMore(3, 13, blocking ? TX_PENDING : 0);
+ expectJust(2, 11, SNAPSHOT_END);
+ } else if (blocking) { // cannot optimize tx end in blocking mode (just one record in buffer)
expectMore(3, 13, TX_PENDING); // still pending
expectJust(2, 11, 0); // this event delivered to end tx, even though it is not updated and outside sub
- } else
+ } else {
expectJust(3, 13, 0); // optimized TX_END here, because that item at time=2 did not update
+ }
}
@Test
@@ -616,7 +708,12 @@ public void testSnapshotInsertPartSub2() {
distribute(1, 14, 0);
distribute(0, 12, SNAPSHOT_END);
// ---
- expectJust(3, 13, 0); // only one items updates in sub range (no TX)
+ if (unconflated) {
+ expectMore(4, 10, SNAPSHOT_BEGIN);
+ expectJust(3, 13, SNAPSHOT_END);
+ } else {
+ expectJust(3, 13, 0); // only one items updates in sub range (no TX)
+ }
}
@Test
@@ -637,8 +734,15 @@ public void testSnapshotInsertPartSub3() {
distribute(1, 14, 0);
distribute(0, 12, SNAPSHOT_END);
// ---
- expectMore(3, 13, TX_PENDING);
- expectJust(1, 14, 0);
+ if (unconflated) {
+ expectMore(4, 10, SNAPSHOT_BEGIN);
+ expectMore(3, 13, blocking ? TX_PENDING : 0);
+ expectMore(2, 11, blocking ? TX_PENDING : 0);
+ expectJust(1, 14, SNAPSHOT_END);
+ } else {
+ expectMore(3, 13, TX_PENDING);
+ expectJust(1, 14, 0);
+ }
}
@Test
@@ -659,11 +763,17 @@ public void testSnapshotInsertPartSub4() {
distribute(2, 14, 0); // new, no sub
distribute(0, 12, SNAPSHOT_END); // confirm, no sub
// ---
- if (blocking) { // cannot optimize tx end in blocking mode (just one record in buffer)
+ if (unconflated) {
+ expectMore(8, 10, SNAPSHOT_BEGIN);
+ expectMore(6, 13, blocking ? TX_PENDING : 0);
+ expectMore(4, 11, blocking ? TX_PENDING : 0);
+ expectJust(3, 0, SNAPSHOT_END | REMOVE_EVENT);
+ } else if (blocking) { // cannot optimize tx end in blocking mode (just one record in buffer)
expectMore(6, 13, TX_PENDING); // still pending
expectJust(Long.MAX_VALUE, 0, REMOVE_EVENT); // this event ends tx even though it is below sub
- } else
+ } else {
expectJust(6, 13, 0); // optimized tx end
+ }
}
@Test
@@ -684,12 +794,20 @@ public void testSnapshotInsertPartSub5() {
distribute(2, 14, 0); // new
distribute(0, 12, SNAPSHOT_END); // confirm, no sub
// ---
- expectMore(6, 13, TX_PENDING);
- if (blocking) { // cannot optimize tx end in blocking mode (just one record in buffer)
+ if (unconflated) {
+ expectMore(8, 10, SNAPSHOT_BEGIN);
+ expectMore(6, 13, blocking ? TX_PENDING : 0);
+ expectMore(4, 11, blocking ? TX_PENDING : 0);
+ expectMore(2, 14, blocking ? TX_PENDING : 0);
+ expectJust(1, 0, SNAPSHOT_END | REMOVE_EVENT);
+ } else if (blocking) { // cannot optimize tx end in blocking mode (just one record in buffer)
+ expectMore(6, 13, TX_PENDING);
expectMore(2, 14, TX_PENDING); // still pending
expectJust(Long.MAX_VALUE, 0, REMOVE_EVENT); // tx end, even though it is below sub AND did not update anything
- } else
+ } else {
+ expectMore(6, 13, TX_PENDING);
expectJust(2, 14, 0); // optimized tx end
+ }
}
@Test
@@ -736,7 +854,12 @@ public void testTxConflate() {
distribute(4, 13, TX_PENDING);
distribute(4, 14, 0);
//--
- expectJust(4, 14, 0);
+ if (unconflated) {
+ expectMore(4, 13, TX_PENDING);
+ expectJust(4, 14, 0);
+ } else {
+ expectJust(4, 14, 0);
+ }
}
@Test
@@ -1032,13 +1155,17 @@ public void testSnapshotUpdateTxEndDeliveryBelowTimeSub1() {
// update snapshot (drop all items)
distribute(0, 0, SNAPSHOT_BEGIN | REMOVE_EVENT | SNAPSHOT_END);
// --
- expectMore(4, 0, REMOVE_EVENT | TX_PENDING);
- expectMore(3, 0, REMOVE_EVENT | TX_PENDING);
- if (blocking) {
+ if (unconflated) {
+ expectJust(2, 0, SNAPSHOT_BEGIN | REMOVE_EVENT | SNAPSHOT_END);
+ } else if (blocking) {
+ expectMore(4, 0, REMOVE_EVENT | TX_PENDING);
+ expectMore(3, 0, REMOVE_EVENT | TX_PENDING);
// will be split into two events
expectMore(2, 0, REMOVE_EVENT | TX_PENDING);
expectJust(Long.MAX_VALUE, 0, REMOVE_EVENT);
} else {
+ expectMore(4, 0, REMOVE_EVENT | TX_PENDING);
+ expectMore(3, 0, REMOVE_EVENT | TX_PENDING);
// tx end will collapse into one event
expectJust(2, 0, REMOVE_EVENT); // no more dirty at this item
}
@@ -1058,12 +1185,16 @@ public void testSnapshotUpdateTxEndDeliveryBelowTimeSub2() {
// update snapshot (drop all items)
distribute(0, 0, SNAPSHOT_BEGIN | REMOVE_EVENT | SNAPSHOT_END);
// --
- expectMore(4, 0, REMOVE_EVENT | TX_PENDING);
- if (blocking) { // cannot optimize tx end in blocking mode (just one record in buffer)
+ if (unconflated) {
+ expectJust(2, 0, SNAPSHOT_BEGIN | REMOVE_EVENT | SNAPSHOT_END);
+ } else if (blocking) { // cannot optimize tx end in blocking mode (just one record in buffer)
+ expectMore(4, 0, REMOVE_EVENT | TX_PENDING);
expectMore(3, 0, REMOVE_EVENT | TX_PENDING); // still pending
expectJust(Long.MAX_VALUE, 0, REMOVE_EVENT); // event to end tx
- } else
+ } else {
+ expectMore(4, 0, REMOVE_EVENT | TX_PENDING);
expectJust(3, 0, REMOVE_EVENT); // optimized tx end here
+ }
}
@Test
@@ -1093,12 +1224,30 @@ public void testFlagsOnLastEventConflationWithRebase() {
history.forceRebase(agent);
distribute(0, 0, SNAPSHOT_END | REMOVE_EVENT); // virtual snapshot end
// now retrieve updates
- expectMore(2, 14, TX_PENDING); // this was in TX
- if (blocking) { // cannot optimize tx end in blocking mode (just one record in buffer)
+ if (unconflated && blocking) {
+ expectMore(2, 14, TX_PENDING);
+ expectMore(1, 15, 0);
+ expectMore(4, 10, SNAPSHOT_BEGIN);
+ expectMore(3, 11, 0);
+ expectMore(2, 14, 0);
+ expectMore(1, 16, TX_PENDING);
+ expectJust(0, 0, SNAPSHOT_END | REMOVE_EVENT);
+ } else if (unconflated) {
+ expectMore(4, 10, SNAPSHOT_BEGIN);
+ expectMore(3, 11, 0);
+ expectMore(2, 14, 0);
+ expectMore(1, 16, 0);
+ expectMore(0, 0, SNAPSHOT_END | REMOVE_EVENT);
+ // tricky case, where "available" is true, but nothing retrieves, because events were unlinked
+ expectNothingRetrieves();
+ } else if (blocking) { // cannot optimize tx end in blocking mode (just one record in buffer)
+ expectMore(2, 14, TX_PENDING); // this was in TX
expectMore(1, 16, TX_PENDING); // still pending
expectJust(0, 0, REMOVE_EVENT); // explicit remove event to end tx
- } else
+ } else {
+ expectMore(2, 14, TX_PENDING); // this was in TX
expectJust(1, 16, 0); // and this in TX (conflated to a single update and optimized to end tx)
+ }
}
@Test
@@ -1131,22 +1280,39 @@ public void testComplexTxUpdateSequence() {
// and one more update separately on the last updated time
distribute(2, 17, 0);
// ---
- expectMore(3, 17, TX_PENDING); // update this
- if (blocking) { // cannot optimize tx end in blocking mode (just one record in buffer)
- expectMore(2, 16, TX_PENDING); // pending
- expectMore(0, 0, REMOVE_EVENT); // explicit tx end with remove event
- expectJust(2, 17, 0); // one more update
- } else
- expectJust(2, 17, 0); // and this ends transaction (optimization and conflation)
- // do transaction that if finished by remove event
- distribute(2, 18, TX_PENDING);
- distribute(0, 0, REMOVE_EVENT);
- // ---
- if (blocking) { // cannot optimize tx end in blocking mode (just one record in buffer)
- expectMore(2, 18, TX_PENDING); // pending
- expectJust(0, 0, REMOVE_EVENT); // delivered tx end
- } else
- expectJust(2, 18, 0); // optimized end of transaction
+ if (unconflated && blocking) {
+ expectMore(4, 10, SNAPSHOT_BEGIN);
+ expectMore(3, 17, TX_PENDING);
+ expectMore(2, 16, TX_PENDING);
+ expectMore(1, 15, TX_PENDING);
+ expectMore(0, 0, REMOVE_EVENT | SNAPSHOT_END);
+ expectJust(2, 17, 0);
+ } else if (unconflated) {
+ expectMore(4, 10, SNAPSHOT_BEGIN);
+ expectMore(3, 17, 0);
+ expectMore(2, 17, 0);
+ expectMore(1, 15, 0);
+ expectJust(0, 0, REMOVE_EVENT | SNAPSHOT_END);
+ } else {
+ expectMore(3, 17, TX_PENDING); // update this
+ if (blocking) { // cannot optimize tx end in blocking mode (just one record in buffer)
+ expectMore(2, 16, TX_PENDING); // pending
+ expectMore(0, 0, REMOVE_EVENT); // explicit tx end with remove event
+ expectJust(2, 17, 0); // one more update
+ } else {
+ expectJust(2, 17, 0); // and this ends transaction (optimization and conflation)
+ }
+ // do transaction that if finished by remove event
+ distribute(2, 18, TX_PENDING);
+ distribute(0, 0, REMOVE_EVENT);
+ // ---
+ if (blocking) { // cannot optimize tx end in blocking mode (just one record in buffer)
+ expectMore(2, 18, TX_PENDING); // pending
+ expectJust(0, 0, REMOVE_EVENT); // delivered tx end
+ } else {
+ expectJust(2, 18, 0); // optimized end of transaction
+ }
+ }
}
@Test
@@ -1183,8 +1349,16 @@ public void testTxUnSubPart() {
distribute(1, 13, 0);
distribute(0, 0, SNAPSHOT_END | REMOVE_EVENT); // virtual snapshot end
// ---
- expectMore(1, 13, 0);
- expectJust(0, 0, SNAPSHOT_END | REMOVE_EVENT); // virtual snapshot end
+ if (unconflated) {
+ expectMore(4, 10, SNAPSHOT_BEGIN);
+ expectMore(3, 11, 0);
+ expectMore(2, 12, 0);
+ expectMore(1, 13, 0);
+ expectJust(0, 0, SNAPSHOT_END | REMOVE_EVENT); // virtual snapshot end
+ } else {
+ expectMore(1, 13, 0);
+ expectJust(0, 0, SNAPSHOT_END | REMOVE_EVENT); // virtual snapshot end
+ }
}
@Test
@@ -1343,6 +1517,9 @@ public void testSnapshotModeThenSnip() {
// test that HB resize and buffer refilter logic all works correctly
@Test
public void testBigSnapshot() {
+ if (unconflated)
+ return;
+
createAgent(0, true);
// submit big snapshot
int n = 100;
@@ -1598,59 +1775,78 @@ public void testExamineDuringSnapshotUpdate() {
distribute(2, 11, 0);
distribute(1, 13, 0);
// ---
- expectJust(1, 13, TX_PENDING);
+ if (unconflated) {
+ // Snapshot is not yet completed - TX_PENDING flag is set (similar to examineAll() below)
+ expectMore(3, 10, (blocking ? 0 : TX_PENDING) | SNAPSHOT_BEGIN);
+ expectMore(2, 11, (blocking ? 0 : TX_PENDING));
+ expectJust(1, 13, TX_PENDING);
+ } else {
+ expectJust(1, 13, TX_PENDING);
+ }
+
// now examine all data
examineAll();
expectMore(3, 10, TX_PENDING | SNAPSHOT_BEGIN);
expectMore(2, 11, TX_PENDING);
expectMore(1, 13, TX_PENDING);
expectJust(0, 0, TX_PENDING | REMOVE_EVENT | SNAPSHOT_END);
+
// now examine by subscription
examineBySubscription(0);
expectMore(3, 10, TX_PENDING | SNAPSHOT_BEGIN);
expectMore(2, 11, TX_PENDING);
expectMore(1, 13, TX_PENDING);
expectJust(0, 0, TX_PENDING | REMOVE_EVENT | SNAPSHOT_END);
+
// now examine by subscription (time 2)
examineBySubscription(2);
expectMore(3, 10, TX_PENDING | SNAPSHOT_BEGIN);
expectJust(2, 11, TX_PENDING | SNAPSHOT_END);
+
// now examine data range 0->0
examineRange(0, 0);
expectJust(0, 0, REMOVE_EVENT | TX_PENDING);
+
// now examine data range LTR (all)
examineRange(0, Long.MAX_VALUE);
expectMore(0, 0, REMOVE_EVENT | TX_PENDING);
expectMore(1, 13, TX_PENDING);
expectMore(2, 11, TX_PENDING);
expectJust(3, 10, TX_PENDING);
+
// now examine data range LTR 1->2
examineRange(1, 2);
expectMore(1, 13, TX_PENDING);
expectJust(2, 11, TX_PENDING);
+
// now examine data range LTR 2->3
examineRange(2, 3);
expectMore(2, 11, TX_PENDING);
expectJust(3, 10, TX_PENDING);
+
// now examine data range RTL (all)
examineRange(Long.MAX_VALUE, 0);
expectMore(3, 10, TX_PENDING);
expectMore(2, 11, TX_PENDING);
expectMore(1, 13, TX_PENDING);
expectJust(0, 0, REMOVE_EVENT | TX_PENDING);
+
// now examine data range RTL 2->1
examineRange(2, 1);
expectMore(2, 11, TX_PENDING);
expectJust(1, 13, TX_PENDING);
+
// now examine data range RTL 2->0
examineRange(2, 0);
expectMore(2, 11, TX_PENDING);
expectMore(1, 13, TX_PENDING);
expectJust(0, 0, REMOVE_EVENT | TX_PENDING);
+
// now examine data range RTL 3->2
examineRange(3, 2);
expectMore(3, 10, TX_PENDING);
expectJust(2, 11, TX_PENDING);
+
// close agent and examine what was there
closeAgentAndExamine();
expectMore(3, 10, TX_PENDING | SNAPSHOT_BEGIN);
@@ -1672,23 +1868,41 @@ public void testSnapshotThenSnip() {
expectMore(2, 11, 0);
expectMore(1, 12, 0);
expectJust(0, 0, REMOVE_EVENT | SNAPSHOT_END);
+
// resend previous snapshot, but snip
distribute(3, 10, SNAPSHOT_BEGIN);
distribute(2, 11, 0);
distribute(1, 12, SNAPSHOT_SNIP);
// ---
- expectJust(1, 12, SNAPSHOT_SNIP);
+ if (unconflated) {
+ expectMore(3, 10, SNAPSHOT_BEGIN);
+ expectMore(2, 11, 0);
+ expectJust(1, 12, SNAPSHOT_SNIP);
+ } else {
+ expectJust(1, 12, SNAPSHOT_SNIP);
+ }
+
// snip even more
distribute(3, 10, SNAPSHOT_BEGIN | SNAPSHOT_SNIP);
// ---
- expectJust(3, 10, SNAPSHOT_SNIP);
+ if (unconflated) {
+ expectJust(3, 10, SNAPSHOT_BEGIN | SNAPSHOT_SNIP);
+ } else {
+ expectJust(3, 10, SNAPSHOT_SNIP);
+ }
// then increase snapshot again (send more data after snip)
distribute(3, 10, SNAPSHOT_BEGIN);
distribute(2, 11, 0);
distribute(1, 12, SNAPSHOT_SNIP); // should be treated as snapshot_end (terminate snapshot update tx)
// ---
- expectMore(2, 11, 0); // extending previous snapshot (no tx needed)
- expectJust(1, 12, SNAPSHOT_SNIP); // snapshot snip makes it consistent
+ if (unconflated) {
+ expectMore(3, 10, (blocking ? SNAPSHOT_SNIP : 0) | SNAPSHOT_BEGIN);
+ expectMore(2, 11, 0);
+ expectJust(1, 12, SNAPSHOT_SNIP);
+ } else {
+ expectMore(2, 11, 0); // extending previous snapshot (no tx needed)
+ expectJust(1, 12, SNAPSHOT_SNIP); // snapshot snip makes it consistent
+ }
// increase snapshot to sub time (no longer snip)
distribute(0, 13, SNAPSHOT_END);
// ---
@@ -1885,17 +2099,24 @@ public void testSnipTimeKnown() {
distribute(2, 14, 0);
distribute(1, 15, 0);
distribute(0, 0, REMOVE_EVENT | SNAPSHOT_END);
- // it must be received from local buffer as transactional update to the current snapshot
- expectMore(6, 0, REMOVE_EVENT | TX_PENDING);
- expectMore(5, 0, REMOVE_EVENT | TX_PENDING);
- expectMore(4, 0, REMOVE_EVENT | TX_PENDING);
- expectMore(3, 13, TX_PENDING);
- expectMore(2, 14, TX_PENDING);
- if (blocking) {
- expectMore(1, 15, TX_PENDING);
- expectJust(0, 0, REMOVE_EVENT);
+ if (unconflated) {
+ expectMore(3, 13, (blocking ? TX_PENDING : 0) | SNAPSHOT_BEGIN);
+ expectMore(2, 14, (blocking ? TX_PENDING : 0));
+ expectMore(1, 15, (blocking ? TX_PENDING : 0));
+ expectJust(0, 0, REMOVE_EVENT | SNAPSHOT_END);
} else {
- expectJust(1, 15, 0); // last event ends transaction when non-blocking retrieve (from HB)
+ // it must be received from local buffer as transactional update to the current snapshot
+ expectMore(6, 0, REMOVE_EVENT | TX_PENDING);
+ expectMore(5, 0, REMOVE_EVENT | TX_PENDING);
+ expectMore(4, 0, REMOVE_EVENT | TX_PENDING);
+ expectMore(3, 13, TX_PENDING);
+ expectMore(2, 14, TX_PENDING);
+ if (blocking) {
+ expectMore(1, 15, TX_PENDING);
+ expectJust(0, 0, REMOVE_EVENT);
+ } else {
+ expectJust(1, 15, 0); // last event ends transaction when non-blocking retrieve (from HB)
+ }
}
}
@@ -1914,11 +2135,17 @@ public void testSnipWithPartialRetrieve() {
// send different snapshot, up to time=4
distribute(6, 13, SNAPSHOT_BEGIN);
distribute(5, 14, 0);
- distribute(4, 15, SNAPSHOT_SNIP); // snip to time > timeKnow must advance timeKnow to timeSub
+ distribute(4, 15, SNAPSHOT_SNIP); // snip to time > timeKnown must advance timeKnown to timeSub
// ---
- expectMore(6, 13, TX_PENDING);
- expectMore(5, 14, TX_PENDING);
- expectJust(4, 15, SNAPSHOT_SNIP); // must end transaction
+ if (unconflated) {
+ expectMore(6, 13, SNAPSHOT_BEGIN);
+ expectMore(5, 14, 0);
+ expectJust(4, 15, SNAPSHOT_SNIP); // must end transaction
+ } else {
+ expectMore(6, 13, TX_PENDING);
+ expectMore(5, 14, TX_PENDING);
+ expectJust(4, 15, SNAPSHOT_SNIP); // must end transaction
+ }
}
@Test
@@ -1954,12 +2181,20 @@ public void testSnipSubSnapshotSweepRemove() {
// send snapshot with only 1 items left (confirm it)
distribute(1, 13, SNAPSHOT_BEGIN);
// ---
- expectMore(4, 0, REMOVE_EVENT | TX_PENDING);
- expectMore(3, 0, REMOVE_EVENT | TX_PENDING);
- expectJust(2, 0, REMOVE_EVENT | TX_PENDING);
+ if (unconflated) {
+ expectJust(1, 13, TX_PENDING | SNAPSHOT_BEGIN);
+ } else {
+ expectMore(4, 0, REMOVE_EVENT | TX_PENDING);
+ expectMore(3, 0, REMOVE_EVENT | TX_PENDING);
+ expectJust(2, 0, REMOVE_EVENT | TX_PENDING);
+ }
// ---
distribute(0, 0, REMOVE_EVENT | SNAPSHOT_END);
- expectJust(0, 0, REMOVE_EVENT);
+ if (unconflated) {
+ expectJust(0, 0, REMOVE_EVENT | SNAPSHOT_SNIP);
+ } else {
+ expectJust(0, 0, REMOVE_EVENT);
+ }
}
@Test
@@ -2032,11 +2267,16 @@ public void testSnipAndSweep() {
expectJust(0, 14, SNAPSHOT_END);
// now sweep-remove and snip in the middle
distribute(2, 0, REMOVE_EVENT | SNAPSHOT_BEGIN | SNAPSHOT_SNIP);
- // remove old events in transaction and snip
- // todo: can be further optimized
- expectMore(4, 0, REMOVE_EVENT | TX_PENDING);
- expectMore(3, 0, REMOVE_EVENT | TX_PENDING);
- expectJust(2, 0, REMOVE_EVENT | SNAPSHOT_SNIP);
+
+ if (unconflated) {
+ expectJust(2, 0, REMOVE_EVENT | SNAPSHOT_BEGIN | SNAPSHOT_SNIP);
+ } else {
+ // remove old events in transaction and snip
+ // todo: can be further optimized
+ expectMore(4, 0, REMOVE_EVENT | TX_PENDING);
+ expectMore(3, 0, REMOVE_EVENT | TX_PENDING);
+ expectJust(2, 0, REMOVE_EVENT | SNAPSHOT_SNIP);
+ }
}
@Test
@@ -2279,7 +2519,7 @@ public void testLostTxPending3() {
// this case was found by MTStressTest
@Test
public void testLostTxPending4() {
- if (blocking)
+ if (blocking || unconflated)
return; // cannot do this test in blocking mode -- it explicitly tests proper buffering
createAgent(-2, true); // agent subscribes from -2, but subscription gets snipped to zero
// snapshot begin
@@ -2367,6 +2607,7 @@ public void testLostTxPending6() {
expectMore(3, 10, SNAPSHOT_BEGIN);
expectMore(2, 11, 0);
expectJust(1, 12, SNAPSHOT_END);
+
// start sending the other snapshot (without update on the first items)
distribute(3, 10, SNAPSHOT_BEGIN); // no update at time=3
// unsubscribe the other agent
@@ -2375,22 +2616,40 @@ public void testLostTxPending6() {
distribute(2, 13, 0);
distribute(1, 14, 0);
distribute(0, 15, SNAPSHOT_END); // no sub any more
+
// do update transaction
distribute(2, 16, TX_PENDING);
distribute(3, 17, TX_PENDING); // now update at time=3 (the only non-ignored event)
distribute(1, 18, 0); // txEnd
+
// and finally distribute a snapshot
distribute(3, 17, SNAPSHOT_BEGIN); // same as in tx above
distribute(2, 19, 0);
distribute(1, 20, SNAPSHOT_END);
// --
- expectMore(2, 13, TX_PENDING);
- expectMore(1, 14, TX_PENDING);
- expectMore(2, 16, TX_PENDING);
- expectMore(3, 17, TX_PENDING);
- expectMore(1, 18, TX_PENDING);
- expectMore(2, 19, TX_PENDING);
- expectJust(1, 20, 0);
+ if (unconflated && blocking) {
+ expectMore(3, 10, SNAPSHOT_BEGIN);
+ expectMore(2, 13, TX_PENDING);
+ expectMore(1, 14, TX_PENDING | SNAPSHOT_END);
+ expectMore(2, 16, TX_PENDING);
+ expectMore(3, 17, TX_PENDING);
+ expectMore(1, 18, TX_PENDING);
+ expectMore(3, 17, TX_PENDING | SNAPSHOT_BEGIN);
+ expectMore(2, 19, TX_PENDING);
+ expectJust(1, 20, SNAPSHOT_END);
+ } else if (unconflated) {
+ expectMore(3, 17, SNAPSHOT_BEGIN);
+ expectMore(2, 19, 0);
+ expectJust(1, 20, SNAPSHOT_END);
+ } else {
+ expectMore(2, 13, TX_PENDING);
+ expectMore(1, 14, TX_PENDING);
+ expectMore(2, 16, TX_PENDING);
+ expectMore(3, 17, TX_PENDING);
+ expectMore(1, 18, TX_PENDING);
+ expectMore(2, 19, TX_PENDING);
+ expectJust(1, 20, 0);
+ }
}
// this case was found by MTStressTest
@@ -2473,7 +2732,12 @@ public void testAgent2WithLargerSub() {
distribute(1, 12, 0);
distribute(0, 13, SNAPSHOT_END);
// ---
- expectNothing();
+ if (unconflated) {
+ expectMore(3, 10, SNAPSHOT_BEGIN);
+ expectJust(2, 11, SNAPSHOT_END);
+ } else {
+ expectNothing();
+ }
}
@Test
@@ -2488,6 +2752,7 @@ public void testAgent2StillConsistentOnTotalSubReduce() {
expectMore(4, 10, SNAPSHOT_BEGIN);
expectMore(3, 11, 0);
expectJust(2, 12, SNAPSHOT_END);
+
// now agent2 comes and subscribes for larger time interval
createAgent2(0, true);
// data source still sends updates on smaller subscription and they must be received by original agent
@@ -2498,14 +2763,22 @@ public void testAgent2StillConsistentOnTotalSubReduce() {
expectMore(4, 13, TX_PENDING);
expectMore(3, 14, TX_PENDING);
expectJust(2, 15, 0);
+
// now snapshot for agent2 is sent
distribute(4, 13, SNAPSHOT_BEGIN);
distribute(3, 14, 0);
distribute(2, 15, 0);
distribute(1, 16, 0);
distribute(0, 17, SNAPSHOT_END);
- // nothing new for original agent, though
- expectNothing();
+ if (unconflated) {
+ expectMore(4, 13, SNAPSHOT_BEGIN);
+ expectMore(3, 14, 0);
+ expectJust(2, 15, SNAPSHOT_END);
+ } else {
+ // nothing new for original agent, though
+ expectNothing();
+ }
+
// now agent2 resubscribes for smaller time interval
setSubTime2(10);
// data source still sends updates and they must be received by original agent
@@ -2516,12 +2789,19 @@ public void testAgent2StillConsistentOnTotalSubReduce() {
expectMore(4, 18, TX_PENDING);
expectMore(3, 19, TX_PENDING);
expectJust(2, 20, 0);
+
// now snapshot is resent because of subscription change
distribute(4, 18, SNAPSHOT_BEGIN);
distribute(3, 19, 0);
distribute(2, 20, SNAPSHOT_END);
- // nothing new for original agent, though
- expectNothing();
+ if (unconflated) {
+ expectMore(4, 18, SNAPSHOT_BEGIN);
+ expectMore(3, 19, 0);
+ expectJust(2, 20, SNAPSHOT_END);
+ } else {
+ // nothing new for original agent, though
+ expectNothing();
+ }
}
@Test
@@ -2547,6 +2827,9 @@ public void testNonSubscribedUpdates() {
distribute(1, 16, 0);
distribute(0, 17, SNAPSHOT_END);
// ---
+ if (unconflated && blocking) {
+ expectMore(10, 0, SNAPSHOT_BEGIN | SNAPSHOT_END | REMOVE_EVENT);
+ }
expectJust(10, 0, SNAPSHOT_BEGIN | SNAPSHOT_END | REMOVE_EVENT);
// now close agent and reopen at time 0. Make sure valid snapshot is only up to time 10
closeAgent();
@@ -2590,11 +2873,16 @@ public void testLowerSnapshot() {
// distribute snapshot at lower values
distribute(1, 12, SNAPSHOT_BEGIN);
distribute(0, 13, SNAPSHOT_END);
- // -- expect snapshot first, then updates (removes)
- expectMore(1, 12, TX_PENDING);
- expectMore(0, 13, TX_PENDING | SNAPSHOT_END);
- expectMore(3, 0, TX_PENDING | REMOVE_EVENT);
- expectJust(2, 0, REMOVE_EVENT);
+ if (unconflated) {
+ expectMore(1, 12, SNAPSHOT_BEGIN);
+ expectJust(0, 13, SNAPSHOT_END);
+ } else {
+ // -- expect snapshot first, then updates (removes)
+ expectMore(1, 12, TX_PENDING);
+ expectMore(0, 13, TX_PENDING | SNAPSHOT_END);
+ expectMore(3, 0, TX_PENDING | REMOVE_EVENT);
+ expectJust(2, 0, REMOVE_EVENT);
+ }
}
/*
@@ -2705,6 +2993,8 @@ void closeAgent() {
agent.close();
agent = null;
provider = null;
+ blockingProvider = null;
+ blockingListener = null;
assertTrue("!available", !available);
}
@@ -2761,7 +3051,64 @@ private void closeAgentAndExamine() {
// HistoryTxBlockingTest overrides
RecordProvider getProvider(QDAgent agent) {
- return agent;
+ // Non-blocking mode: do nothing
+ if (!blocking)
+ return agent;
+
+ // Blocking mode:
+ // Buffer size is limited to 1 and blocking is configured,
+ // while the actual buffer is off-loaded to a separate buffer.
+ // This way buffer blocking in History is stress-tested.
+ agent.setBufferOverflowStrategy(QDAgent.BufferOverflowStrategy.BLOCK);
+ agent.setMaxBufferSize(1);
+
+ // our buffer
+ final RecordBuffer buf = RecordBuffer.getInstance(agent.getMode());
+ // our provider
+ blockingProvider = new AbstractRecordProvider() {
+ @Override
+ public RecordMode getMode() {
+ return agent.getMode();
+ }
+
+ @Override
+ public boolean retrieve(RecordSink sink) {
+ return buf.retrieve(sink);
+ }
+
+ @Override
+ public void setRecordListener(RecordListener listener) {
+ blockingListener = listener;
+ if (listener != null && buf.hasNext())
+ listener.recordsAvailable(blockingProvider);
+ }
+ };
+ // will mimic conflation logic
+ final RecordSink conflatingSink = new AbstractRecordSink() {
+ long lastPosition = -1;
+ @Override
+ public void append(RecordCursor cursor) {
+ if (lastPosition >= buf.getPosition()) {
+ RecordCursor writeCursor = buf.writeCursorAt(lastPosition);
+ if (writeCursor.getTime() == cursor.getTime() && !unconflated) {
+ // Emulate conflation
+ writeCursor.setEventFlags(cursor.getEventFlags());
+ writeCursor.copyDataFrom(cursor);
+ return;
+ }
+ }
+ lastPosition = buf.getLimit();
+ buf.append(cursor);
+ }
+ };
+ // install an agent's listener
+ agent.setRecordListener(p -> {
+ boolean wasEmpty = !buf.hasNext();
+ agent.retrieve(conflatingSink);
+ if (wasEmpty && buf.hasNext() && blockingListener != null)
+ blockingListener.recordsAvailable(blockingProvider);
+ });
+ return blockingProvider;
}
private void startBatch() {
@@ -2783,7 +3130,6 @@ private void processBatch() {
distributeBatch = false;
}
-
private void removeData() {
RecordBuffer buf = RecordBuffer.getInstance(RecordMode.FLAGGED_DATA);
buf.add(RECORD, CIPHER, null);
@@ -2801,14 +3147,21 @@ private void expectJust(long time, int value, int flags) {
assertTrue("!available", !available && retrieveBuf.isEmpty());
}
+ private void expectUnconflated(final long time, final int value, final int flags) {
+ if (unconflated) {
+ expect(time, value, flags);
+ }
+ }
+
private void expect(final long time, final int value, final int flags) {
assertTrue("available", available || !retrieveBuf.isEmpty());
if (retrieveBuf.isEmpty())
retrieveBatch(1);
RecordCursor cursor = retrieveBuf.next();
+ assertNotNull(cursor);
assertEquals("record", RECORD, cursor.getRecord());
assertEquals("cipher", CIPHER, cursor.getCipher());
- assertEquals("symbol", null, cursor.getSymbol());
+ assertNull("symbol", cursor.getSymbol());
assertEquals("time", time, cursor.getTime());
assertEquals("value", value, cursor.getInt(VALUE_INDEX));
assertEquals("flags", flags, cursor.getEventFlags());
@@ -2856,7 +3209,7 @@ public void append(RecordCursor cursor) {
}
});
assertEquals("received", size, retrieveBuf.size());
- assertTrue("!available", !available);
+ assertFalse("!available", available);
available = hasMore;
}
@@ -2876,8 +3229,13 @@ public int getMaxRecordCount(DataRecord record, int cipher, String symbol) {
private class HistoryImpl extends History {
boolean forceRetrieveUpdate;
- HistoryImpl(Builder builder) {
- super(builder);
+ HistoryImpl(Builder builder) {
+ super(builder, new RecordOnlyFilter(builder.getScheme()) {
+ @Override
+ public boolean acceptRecord(DataRecord record) {
+ return !unconflated;
+ }
+ });
}
@Override
diff --git a/qd-core/src/test/java/com/devexperts/qd/test/HistoryAddRemoveSnapshotTest.java b/qd-core/src/test/java/com/devexperts/qd/test/HistoryAddRemoveSnapshotTest.java
index 278e99afc..26dc5ae96 100644
--- a/qd-core/src/test/java/com/devexperts/qd/test/HistoryAddRemoveSnapshotTest.java
+++ b/qd-core/src/test/java/com/devexperts/qd/test/HistoryAddRemoveSnapshotTest.java
@@ -45,7 +45,7 @@ public class HistoryAddRemoveSnapshotTest extends TestCase {
private static final PentaCodec CODEC = PentaCodec.INSTANCE;
private static final DataScheme SCHEME = new DefaultScheme(CODEC, RECORD);
- private final QDHistory history = QDFactory.getDefaultFactory().createHistory(SCHEME);
+ private final QDHistory history = QDFactory.getDefaultFactory().historyBuilder().withScheme(SCHEME).build();
private final QDDistributor distributor = history.distributorBuilder().build();
private QDAgent agent = history.agentBuilder().build();
diff --git a/qd-dataextractor/pom.xml b/qd-dataextractor/pom.xml
index 67371b126..8f61ee1b4 100644
--- a/qd-dataextractor/pom.xml
+++ b/qd-dataextractor/pom.xml
@@ -14,7 +14,7 @@
QD
com.devexperts.qd
- 3.291
+ 3.292
4.0.0
diff --git a/qd-logger/pom.xml b/qd-logger/pom.xml
index b76fa3918..42e8129a0 100644
--- a/qd-logger/pom.xml
+++ b/qd-logger/pom.xml
@@ -14,7 +14,7 @@
QD
com.devexperts.qd
- 3.291
+ 3.292
../pom.xml
4.0.0
diff --git a/qd-nio/pom.xml b/qd-nio/pom.xml
index 8fe4ed17e..287564b78 100644
--- a/qd-nio/pom.xml
+++ b/qd-nio/pom.xml
@@ -14,7 +14,7 @@
QD
com.devexperts.qd
- 3.291
+ 3.292
../pom.xml
4.0.0
diff --git a/qd-rmi/pom.xml b/qd-rmi/pom.xml
index 0a91bce4f..c94a7478b 100644
--- a/qd-rmi/pom.xml
+++ b/qd-rmi/pom.xml
@@ -14,7 +14,7 @@
QD
com.devexperts.qd
- 3.291
+ 3.292
../pom.xml
4.0.0
diff --git a/qd-sample/pom.xml b/qd-sample/pom.xml
index c1d958969..c81147f9a 100644
--- a/qd-sample/pom.xml
+++ b/qd-sample/pom.xml
@@ -14,7 +14,7 @@
QD
com.devexperts.qd
- 3.291
+ 3.292
../pom.xml
4.0.0
diff --git a/qd-sample/src/test/java/com/devexperts/qd/test/FieldAdaptationTest.java b/qd-sample/src/test/java/com/devexperts/qd/test/FieldAdaptationTest.java
index c8b1e0029..8834f5d31 100644
--- a/qd-sample/src/test/java/com/devexperts/qd/test/FieldAdaptationTest.java
+++ b/qd-sample/src/test/java/com/devexperts/qd/test/FieldAdaptationTest.java
@@ -19,40 +19,224 @@
import com.devexperts.qd.kit.CompactIntField;
import com.devexperts.qd.kit.DecimalField;
import com.devexperts.qd.kit.DefaultRecord;
+import com.devexperts.qd.kit.LongField;
+import com.devexperts.qd.kit.PlainIntField;
+import com.devexperts.qd.kit.TimeMillisField;
+import com.devexperts.qd.kit.TimeSecondsField;
+import com.devexperts.qd.kit.VoidIntField;
+import com.devexperts.qd.kit.WideDecimalField;
import com.devexperts.qd.ng.RecordBuffer;
import com.devexperts.qd.ng.RecordCursor;
import com.devexperts.qd.qtp.BinaryQTPComposer;
import com.devexperts.qd.qtp.BinaryQTPParser;
import com.devexperts.qd.qtp.MessageConsumerAdapter;
import com.devexperts.qd.qtp.MessageType;
-import com.devexperts.qd.util.Decimal;
-import junit.framework.TestCase;
+import com.devexperts.util.TimeFormat;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
-public class FieldAdaptationTest extends TestCase {
+import java.util.Arrays;
+import java.util.function.BiFunction;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+
+@RunWith(Parameterized.class)
+public class FieldAdaptationTest {
+ static final String RECORD = "Test";
static final String SYMBOL = "TEST";
- static final int VALUE = 123000;
- static final DefaultRecord srcRecord = new DefaultRecord(0, "rec", false, new DataIntField[] {new CompactIntField(0, "rec.i0")}, null);
- static final DataScheme srcScheme = new TestDataScheme(1, 0, TestDataScheme.Type.SIMPLE, srcRecord);
+ // Variable names are fitted into 4 characters for better alignment
+ static final BiFunction PINT = PlainIntField::new;
+ static final BiFunction CINT = CompactIntField::new;
+ static final BiFunction TINY = DecimalField::new;
+ static final BiFunction WIDE = WideDecimalField::new;
+ static final BiFunction LONG = LongField::new;
+ static final BiFunction SECS = TimeSecondsField::new;
+ static final BiFunction MILS = TimeMillisField::new;
+
+ static final String TIME_SECONDS = TimeFormat.DEFAULT.withTimeZone().format(1_000);
+ static final String TIME_MILLIS = TimeFormat.DEFAULT.withTimeZone().withMillis().format(1_000);
+
+ private final DataIntField fromField;
+ private final String fromValue;
+ private final DataIntField toField;
+ private final String toValue;
+
+ @Parameterized.Parameters(name="({index}) {4},from={2},to={3}")
+ public static Iterable parameters() {
+ return Arrays.asList(new Object[][] {
+ { PINT, PINT, 1, 1, "int->int" },
+ { PINT, CINT, 1, 1, "int->compact int" },
+ { PINT, TINY, 1, 1, "int->decimal" },
+ { PINT, WIDE, 1, 1, "int->wide decimal" },
+ { PINT, LONG, 1, 1, "int->long" },
+
+ { PINT, PINT, -1, -1, "int->int" },
+ { PINT, CINT, -1, -1, "int->compact int" },
+ { PINT, TINY, -1, -1, "int->decimal" },
+ { PINT, WIDE, -1, -1, "int->wide decimal" },
+ { PINT, LONG, -1, -1, "int->long" },
+
+ { CINT, PINT, 123456, 123456, "compact int->int" },
+ { CINT, CINT, 123456, 123456, "compact int->compact int" },
+ { CINT, TINY, 123456, 123456, "compact int->decimal" },
+ { CINT, WIDE, 123456, 123456, "compact int->wide decimal" },
+ { CINT, LONG, 123456, 123456, "compact int->long" },
+
+ { TINY, PINT, "1", 1, "decimal->int" },
+ { TINY, CINT, "1", 1, "decimal->compact int" },
+ { TINY, TINY, "1", "1", "decimal->decimal" },
+ { TINY, WIDE, "1", "1", "decimal->wide decimal" },
+ { TINY, LONG, "1", 1L, "decimal->long" },
+
+ { TINY, PINT, "-1", -1, "decimal->int" },
+ { TINY, CINT, "-1", -1, "decimal->compact int" },
+ { TINY, TINY, "-1", "-1", "decimal->decimal" },
+ { TINY, WIDE, "-1", "-1", "decimal->wide decimal" },
+ { TINY, LONG, "-1", -1L, "decimal->long" },
+
+ { TINY, PINT, "NaN", 0, "decimal->int" },
+ { TINY, CINT, "NaN", 0, "decimal->compact int" },
+ { TINY, TINY, "NaN", "NaN", "decimal->decimal" },
+ { TINY, WIDE, "NaN", "NaN", "decimal->wide decimal" },
+ { TINY, LONG, "NaN", 0L, "decimal->long" },
+
+ // Tiny Decimal Infinity conversion
+ { TINY, PINT, "Infinity", Integer.MAX_VALUE, "decimal->int" },
+ { TINY, CINT, "Infinity", Integer.MAX_VALUE, "decimal->compact int" },
+ { TINY, TINY, "Infinity", "Infinity", "decimal->decimal" },
+ { TINY, WIDE, "Infinity", "Infinity", "decimal->wide decimal" },
+ { TINY, LONG, "Infinity", Long.MAX_VALUE, "decimal->long" },
+
+ // Tiny Decimal -Infinity conversion
+ { TINY, PINT, "-Infinity", Integer.MIN_VALUE, "decimal->int" },
+ { TINY, CINT, "-Infinity", Integer.MIN_VALUE, "decimal->compact int" },
+ { TINY, TINY, "-Infinity", "-Infinity", "decimal->decimal" },
+ { TINY, WIDE, "-Infinity", "-Infinity", "decimal->wide decimal" },
+ { TINY, LONG, "-Infinity", Long.MIN_VALUE, "decimal->long" },
+
+ { WIDE, PINT, "1", "1", "wide decimal->int" },
+ { WIDE, CINT, "1", "1", "wide decimal->compact int" },
+ { WIDE, TINY, "1", "1", "wide decimal->decimal" },
+ { WIDE, WIDE, "1", "1", "wide decimal->wide decimal" },
+ { WIDE, LONG, "1", "1", "wide decimal->long" },
+
+ { WIDE, PINT, "-1", "-1", "wide decimal->int" },
+ { WIDE, CINT, "-1", "-1", "wide decimal->compact int" },
+ { WIDE, TINY, "-1", "-1", "wide decimal->decimal" },
+ { WIDE, WIDE, "-1", "-1", "wide decimal->wide decimal" },
+ { WIDE, LONG, "-1", "-1", "wide decimal->long" },
+
+ { WIDE, PINT, "NaN", 0, "wide decimal->int" },
+ { WIDE, CINT, "NaN", 0, "wide decimal->compact int" },
+ { WIDE, TINY, "NaN", "NaN", "wide decimal->decimal" },
+ { WIDE, WIDE, "NaN", "NaN", "wide decimal->wide decimal" },
+ { WIDE, LONG, "NaN", 0L, "wide decimal->long" },
+
+ // Wide Decimal Infinity conversion
+ // Note that (int) Long.MAX_VALUE == -1
+ { WIDE, PINT, "Infinity", (int)Long.MAX_VALUE, "wide decimal->int" },
+ { WIDE, CINT, "Infinity", (int)Long.MAX_VALUE, "wide decimal->compact int" },
+ { WIDE, TINY, "Infinity", "Infinity", "wide decimal->decimal" },
+ { WIDE, WIDE, "Infinity", "Infinity", "wide decimal->wide decimal" },
+ { WIDE, LONG, "Infinity", Long.toString(Long.MAX_VALUE), "wide decimal->long" },
- static final DefaultRecord dstRecord = new DefaultRecord(0, "rec", false, new DataIntField[] {new DecimalField(0, "rec.i0")}, null);
- static final DataScheme dstScheme = new TestDataScheme(1, 0, TestDataScheme.Type.SIMPLE, dstRecord);
+ // Wide Decimal -Infinity conversion
+ // Note that (int) Long.MIN_VALUE == 0
+ { WIDE, PINT, "-Infinity", (int)Long.MIN_VALUE, "wide decimal->int" },
+ { WIDE, CINT, "-Infinity", (int)Long.MIN_VALUE, "wide decimal->compact int" },
+ { WIDE, TINY, "-Infinity", "-Infinity", "wide decimal->decimal" },
+ { WIDE, WIDE, "-Infinity", "-Infinity", "wide decimal->wide decimal" },
+ { WIDE, LONG, "-Infinity", Long.MIN_VALUE, "wide decimal->long" },
+
+ // Truncation & Precision Loss
+ { WIDE, PINT, "1.23456789", "1", "wide decimal->int" },
+ { WIDE, CINT, "1.23456789", "1", "wide decimal->compact int" },
+ { WIDE, TINY, "1.23456789", "1.2345679", "wide decimal->decimal" },
+ { WIDE, WIDE, "1.23456789", "1.23456789", "wide decimal->wide decimal" },
+ { WIDE, LONG, "1.23456789", "1", "wide decimal->long" },
+
+ { LONG, PINT, 1L, 1, "long->int" },
+ { LONG, CINT, 1L, 1, "long->compact int" },
+ { LONG, TINY, 1L, "1", "long->decimal" },
+ { LONG, WIDE, 1L, "1", "long->wide decimal" },
+ { LONG, LONG, 1L, 1L, "long->long" },
+
+ { LONG, PINT, -1L, -1, "long->int" },
+ { LONG, CINT, -1L, -1, "long->compact int" },
+ { LONG, TINY, -1L, "-1", "long->decimal" },
+ { LONG, WIDE, -1L, "-1", "long->wide decimal" },
+ { LONG, LONG, -1L, -1L, "long->long" },
+
+ // Truncation & Precision Loss
+ { LONG, PINT, Integer.MAX_VALUE + 1L, Integer.MIN_VALUE, "long->int" },
+ { LONG, CINT, Integer.MAX_VALUE + 1L, Integer.MIN_VALUE, "long->compact int" },
+ { LONG, TINY, Integer.MAX_VALUE + 1L, "2147483600", "long->decimal" },
+ { LONG, WIDE, Integer.MAX_VALUE + 1L, Integer.MAX_VALUE + 1L, "long->wide decimal" },
+ { LONG, LONG, Integer.MAX_VALUE + 1L, Integer.MAX_VALUE + 1L, "long->long" },
+
+ // Time
+ { SECS, PINT, "0", "0", "time seconds->int" },
+ { SECS, LONG, "0", "0", "time seconds->long" },
+ { SECS, PINT, "19700101-000001+0000", "1", "time seconds->int" },
+ { SECS, LONG, "19700101-000001+0000", "1", "time seconds->long" },
+ { LONG, SECS, 0L, "0", "long->time seconds" },
+ { LONG, SECS, 1L, TIME_SECONDS, "long->time seconds" },
+
+ { MILS, PINT, "0", "0", "time millis->long" },
+ { MILS, LONG, "0", "0", "time millis->long" },
+ { MILS, PINT, "19700101-000001.000+0000", "1000", "time millis->int" },
+ { MILS, LONG, "19700101-000001.000+0000", "1000", "time millis->long" },
+ { LONG, MILS, 0L, "0", "long->time millis" },
+ { LONG, MILS, 1000L, TIME_MILLIS, "long->time millis" },
+
+ { SECS, MILS, "0", "0", "time seconds->time millis" },
+ { SECS, MILS, TIME_SECONDS, TIME_MILLIS, "time seconds->time millis" },
+ { MILS, SECS, TIME_MILLIS, TIME_SECONDS, "time millis->time seconds" },
+ { MILS, SECS, TIME_MILLIS, TIME_SECONDS, "time millis->time seconds" },
+ });
+ }
+
+ public FieldAdaptationTest(
+ BiFunction fromField, BiFunction toField,
+ Object fromValue, Object toValue, @SuppressWarnings("unused") String description)
+ {
+ this.fromField = field(fromField);
+ this.toField = field(toField);
+ this.fromValue = fromValue.toString();
+ this.toValue = toValue.toString();
+ }
+
+ @Test
+ public void testFieldConversion() {
+ DefaultRecord fromRecord = new DefaultRecord(0, RECORD, false, new DataIntField[] {
+ fromField, new VoidIntField(1, RECORD + ".void") }, null); // use extra void field for long values
+ DataScheme fromScheme = new TestDataScheme(1, 0, TestDataScheme.Type.SIMPLE, fromRecord);
+
+ DefaultRecord toRecord = new DefaultRecord(0, RECORD, false, new DataIntField[] {
+ toField, new VoidIntField(1, RECORD + ".void")}, null); // use extra void field for long values
+ DataScheme toScheme = new TestDataScheme(1, 0, TestDataScheme.Type.SIMPLE, toRecord);
+
+ RecordBuffer fromBuffer = new RecordBuffer();
+ RecordCursor fromCursor = fromBuffer.add(fromRecord, fromScheme.getCodec().encode(SYMBOL), SYMBOL);
+ fromField.setString(fromCursor, fromValue);
- public void testFieldAdaptation() {
- RecordBuffer srcBuffer = new RecordBuffer();
- RecordCursor srcCursor = srcBuffer.add(srcRecord, srcScheme.getCodec().encode(SYMBOL), SYMBOL);
- srcCursor.setInt(0, VALUE);
ChunkedOutput output = new ChunkedOutput();
- BinaryQTPComposer composer = new BinaryQTPComposer(srcScheme, true);
+ BinaryQTPComposer composer = new BinaryQTPComposer(fromScheme, true);
composer.setOutput(output);
- assertFalse(composer.visitData(srcBuffer, MessageType.STREAM_DATA));
+ assertFalse(composer.visitData(fromBuffer, MessageType.STREAM_DATA));
// copy composed chunks to chunked input
ChunkedInput input = new ChunkedInput();
input.addAllToInput(output.getOutput(this), this);
// parser from this input
- BinaryQTPParser parser = new BinaryQTPParser(dstScheme);
+ BinaryQTPParser parser = new BinaryQTPParser(toScheme);
parser.setInput(input);
parser.parse(new MessageConsumerAdapter() {
@@ -73,14 +257,18 @@ public void handleUnknownMessage(int messageTypeId) {
@Override
public void processStreamData(DataIterator iterator) {
- RecordBuffer dstBuffer = (RecordBuffer) iterator;
- RecordCursor dstCursor = dstBuffer.next();
- assertNotNull("no data", dstCursor);
- assertEquals("wrong record", dstRecord, dstCursor.getRecord());
- assertEquals("wrong symbol", SYMBOL, dstScheme.getCodec().decode(dstCursor.getCipher(), dstCursor.getSymbol()));
- assertEquals("wrong value", VALUE, Decimal.toDouble(dstCursor.getInt(0)), 0);
- assertNull("extra data", dstBuffer.next());
+ RecordBuffer toBuffer = (RecordBuffer) iterator;
+ RecordCursor toCursor = toBuffer.next();
+ assertNotNull("no data", toCursor);
+ assertEquals("wrong record", toRecord, toCursor.getRecord());
+ assertEquals("wrong symbol", SYMBOL, toScheme.getCodec().decode(toCursor.getCipher(), toCursor.getSymbol()));
+ assertEquals("wrong value", toValue, toField.getString(toCursor));
+ assertNull("extra data", toBuffer.next());
}
});
}
+
+ private static DataIntField field(BiFunction constructor) {
+ return constructor.apply(0, RECORD + ".field");
+ }
}
diff --git a/qd-samplecert/pom.xml b/qd-samplecert/pom.xml
index b366c5e61..720fd9b92 100644
--- a/qd-samplecert/pom.xml
+++ b/qd-samplecert/pom.xml
@@ -14,7 +14,7 @@
QD
com.devexperts.qd
- 3.291
+ 3.292
../pom.xml
4.0.0
diff --git a/qd-servlet/pom.xml b/qd-servlet/pom.xml
index 69a6378d3..7a3d46405 100644
--- a/qd-servlet/pom.xml
+++ b/qd-servlet/pom.xml
@@ -14,7 +14,7 @@
QD
com.devexperts.qd
- 3.291
+ 3.292
../pom.xml
4.0.0
diff --git a/qd-stripe/pom.xml b/qd-stripe/pom.xml
index cd33127f3..f0e128887 100644
--- a/qd-stripe/pom.xml
+++ b/qd-stripe/pom.xml
@@ -14,7 +14,7 @@
QD
com.devexperts.qd
- 3.291
+ 3.292
../pom.xml
4.0.0
diff --git a/qd-tools/pom.xml b/qd-tools/pom.xml
index 969a06fdd..af0c68b2e 100644
--- a/qd-tools/pom.xml
+++ b/qd-tools/pom.xml
@@ -14,7 +14,7 @@
QD
com.devexperts.qd
- 3.291
+ 3.292
../pom.xml
4.0.0
diff --git a/qds-file/pom.xml b/qds-file/pom.xml
index a105a9bcc..20377eeeb 100644
--- a/qds-file/pom.xml
+++ b/qds-file/pom.xml
@@ -14,7 +14,7 @@
QD
com.devexperts.qd
- 3.291
+ 3.292
../pom.xml
4.0.0
diff --git a/qds-file/src/main/java/com/devexperts/qd/qtp/fieldreplacer/TimeFieldReplacer.java b/qds-file/src/main/java/com/devexperts/qd/qtp/fieldreplacer/TimeFieldReplacer.java
index 31b06e6d1..52a9ffe10 100644
--- a/qds-file/src/main/java/com/devexperts/qd/qtp/fieldreplacer/TimeFieldReplacer.java
+++ b/qds-file/src/main/java/com/devexperts/qd/qtp/fieldreplacer/TimeFieldReplacer.java
@@ -47,7 +47,7 @@
*
* current time : replaces time with current. See {@link System#currentTimeMillis()}.
* Configuration format: {@code "current"};
- * increase/decrese time : increases or decreases time on specified delta.
+ * increase/decrease time : increases or decreases time on specified delta.
* Configuration format: {@code "+"} or {@code "-"};
* specified time : replaces time with specified.
* Configuration format: {@code ""}.
diff --git a/qds-monitoring/pom.xml b/qds-monitoring/pom.xml
index 5038639f9..5f98516b5 100644
--- a/qds-monitoring/pom.xml
+++ b/qds-monitoring/pom.xml
@@ -14,7 +14,7 @@
QD
com.devexperts.qd
- 3.291
+ 3.292
../pom.xml
4.0.0
diff --git a/qds-tools/pom.xml b/qds-tools/pom.xml
index 393d270e1..7c0f7555b 100644
--- a/qds-tools/pom.xml
+++ b/qds-tools/pom.xml
@@ -14,7 +14,7 @@
QD
com.devexperts.qd
- 3.291
+ 3.292
../pom.xml
4.0.0
diff --git a/qds/pom.xml b/qds/pom.xml
index 29866f960..584c6643f 100644
--- a/qds/pom.xml
+++ b/qds/pom.xml
@@ -14,7 +14,7 @@
QD
com.devexperts.qd
- 3.291
+ 3.292
../pom.xml
4.0.0
diff --git a/rt-api-builder/pom.xml b/rt-api-builder/pom.xml
index d3d832b45..3b2890c87 100644
--- a/rt-api-builder/pom.xml
+++ b/rt-api-builder/pom.xml
@@ -14,7 +14,7 @@
QD
com.devexperts.qd
- 3.291
+ 3.292
4.0.0
diff --git a/teamcity-version/pom.xml b/teamcity-version/pom.xml
index 33206e06f..fb8ef1f72 100644
--- a/teamcity-version/pom.xml
+++ b/teamcity-version/pom.xml
@@ -14,7 +14,7 @@
QD
com.devexperts.qd
- 3.291
+ 3.292
../pom.xml
4.0.0