Skip to content

Commit 18210de

Browse files
authored
chore: Improve Spanner perf (#130)
* remove equatable * _ * _ * wip * _ * _ * reduce time spent resolving params * _ * _ * _ * remove sync*
1 parent 4e90004 commit 18210de

File tree

6 files changed

+367
-346
lines changed

6 files changed

+367
-346
lines changed
Lines changed: 117 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,20 @@
1-
import 'package:collection/collection.dart';
2-
import 'package:equatable/equatable.dart';
3-
41
import '../tree/node.dart';
2+
import '../tree/tree.dart';
53
import 'descriptor.dart';
64
import 'utils.dart';
75

86
final _knownDescriptors = {'number': numDescriptor};
97

10-
/// build a parametric definition from a route part
11-
ParameterDefinition? _buildParamDefinition(String part, bool terminal) {
12-
if (closeDoorParametricRegex.hasMatch(part)) {
13-
throw ArgumentError.value(
14-
part, null, 'Parameter definition is invalid. Close door neighbors');
15-
}
8+
SingleParameterDefn _singleParamDefn(RegExpMatch m) {
9+
final group = m.group(2)!;
10+
var param = group;
1611

17-
ParameterDefinition makeDefinition(RegExpMatch m, {bool end = false}) {
12+
Iterable<ParameterDescriptor>? descriptors;
13+
14+
if (group.contains('|')) {
1815
final parts = m.group(2)!.split('|');
16+
param = parts.first;
1917

20-
Iterable<ParameterDescriptor>? descriptors;
2118
if (parts.length > 1) {
2219
descriptors = parts.sublist(1).map((e) {
2320
final value = e.isRegex ? regexDescriptor : _knownDescriptors[e];
@@ -28,14 +25,20 @@ ParameterDefinition? _buildParamDefinition(String part, bool terminal) {
2825
return value;
2926
});
3027
}
28+
}
3129

32-
return ParameterDefinition._(
33-
parts.first,
34-
prefix: m.group(1)?.nullIfEmpty,
35-
suffix: m.group(3)?.nullIfEmpty,
36-
terminal: end,
37-
descriptors: descriptors ?? const [],
38-
);
30+
return SingleParameterDefn._(
31+
param,
32+
prefix: m.group(1)?.nullIfEmpty,
33+
suffix: m.group(3)?.nullIfEmpty,
34+
descriptors: descriptors ?? const [],
35+
);
36+
}
37+
38+
ParameterDefinition buildParamDefinition(String part) {
39+
if (closeDoorParametricRegex.hasMatch(part)) {
40+
throw ArgumentError.value(
41+
part, null, 'Parameter definition is invalid. Close door neighbors');
3942
}
4043

4144
final matches = parametricDefnsRegex.allMatches(part);
@@ -44,122 +47,145 @@ ParameterDefinition? _buildParamDefinition(String part, bool terminal) {
4447
}
4548

4649
if (matches.length == 1) {
47-
return makeDefinition(matches.first, end: terminal);
50+
return _singleParamDefn(matches.first);
4851
}
4952

50-
final parent = makeDefinition(matches.first, end: false);
51-
final subdefns = matches.skip(1);
52-
final subparts = subdefns.mapIndexed(
53-
(i, e) => makeDefinition(e, end: i == (subdefns.length - 1) && terminal),
54-
);
53+
final defns = matches.map(_singleParamDefn);
54+
final partsMap = {for (final defn in defns) defn.name: defn};
5555

56-
return CompositeParameterDefinition._(parent, subparts: subparts);
56+
return CompositeParameterDefinition._(partsMap, defns.last.name);
5757
}
5858

59-
class ParameterDefinition with EquatableMixin, HandlerStore {
59+
abstract class ParameterDefinition implements HandlerStore {
60+
String get name;
61+
62+
String get templateStr;
63+
64+
RegExp get template;
65+
66+
String get key;
67+
68+
bool get terminal;
69+
70+
Map<String, dynamic>? resolveParams(String pattern);
71+
}
72+
73+
class SingleParameterDefn extends ParameterDefinition with HandlerStoreMixin {
74+
@override
6075
final String name;
76+
6177
final String? prefix;
6278
final String? suffix;
63-
final bool terminal;
6479

6580
final Iterable<ParameterDescriptor> descriptors;
6681

67-
ParameterDefinition._(
82+
@override
83+
final String templateStr;
84+
85+
@override
86+
late final RegExp template;
87+
88+
@override
89+
String get key => 'prefix=$prefix&suffix=$suffix&terminal=$terminal';
90+
91+
bool _terminal;
92+
93+
@override
94+
bool get terminal => _terminal;
95+
96+
SingleParameterDefn._(
6897
this.name, {
6998
this.descriptors = const [],
7099
this.prefix,
71100
this.suffix,
72-
this.terminal = false,
73-
});
74-
75-
String get templateStr {
76-
String result = '<$name>';
77-
if (prefix != null) result = "$prefix$result";
78-
if (suffix != null) result = '$result$suffix';
79-
return result;
80-
}
81-
82-
RegExp? _paramRegexCache;
83-
RegExp get template {
84-
if (_paramRegexCache != null) return _paramRegexCache!;
85-
return _paramRegexCache = buildRegexFromTemplate(templateStr);
86-
}
87-
88-
factory ParameterDefinition.from(String part, {bool terminal = false}) {
89-
return _buildParamDefinition(part, terminal)!;
101+
}) : templateStr = buildTemplateString(
102+
name: name,
103+
prefix: prefix,
104+
suffix: suffix,
105+
),
106+
_terminal = false {
107+
template = buildRegexFromTemplate(templateStr);
90108
}
91109

92110
bool matches(String pattern) => template.hasMatch(pattern);
93111

94-
bool isExactExceptName(ParameterDefinition defn) {
95-
if (methods.isNotEmpty) {
96-
final hasMethod = defn.methods.any((e) => methods.contains(e));
97-
if (!hasMethod) return false;
98-
}
99-
100-
return prefix == defn.prefix &&
101-
suffix == defn.suffix &&
102-
terminal == defn.terminal;
103-
}
104-
105-
Map<String, dynamic> resolveParams(final String pattern) {
112+
@override
113+
Map<String, dynamic>? resolveParams(final String pattern) {
106114
final params = resolveParamsFromPath(template, pattern);
115+
if (params == null) return null;
116+
107117
return params
108-
..[name] = descriptors.fold<dynamic>(
118+
..[name] = descriptors.fold(
109119
params[name],
110120
(value, descriptor) => descriptor(value),
111121
);
112122
}
113123

114124
@override
115-
List<Object?> get props => [prefix, name, suffix, terminal];
125+
void addRoute<T>(HTTPMethod method, IndexedValue<T> handler) {
126+
super.addRoute(method, handler);
127+
_terminal = true;
128+
}
116129
}
117130

118-
class CompositeParameterDefinition extends ParameterDefinition {
119-
final Iterable<ParameterDefinition> subparts;
131+
class CompositeParameterDefinition extends ParameterDefinition
132+
implements HandlerStore {
133+
final Map<String, SingleParameterDefn> parts;
134+
final String _lastPartKey;
120135

121-
CompositeParameterDefinition._(
122-
ParameterDefinition parent, {
123-
required this.subparts,
124-
}) : super._(
125-
parent.name,
126-
prefix: parent.prefix,
127-
suffix: parent.suffix,
128-
terminal: false,
129-
descriptors: parent.descriptors,
130-
);
136+
SingleParameterDefn get _maybeTerminalPart => parts[_lastPartKey]!;
137+
138+
CompositeParameterDefinition._(this.parts, this._lastPartKey);
131139

132140
@override
133-
List<Object?> get props => [super.props, ...subparts];
141+
String get templateStr => parts.values.map((e) => e.templateStr).join();
134142

135143
@override
136-
bool get terminal => subparts.any((e) => e.terminal);
144+
String get name => parts.values.map((e) => e.name).join('|');
137145

138146
@override
139-
String get templateStr {
140-
return '${super.templateStr}${subparts.map((e) => e.templateStr).join()}';
141-
}
147+
String get key => parts.values.map((e) => e.key).join('|');
142148

143149
@override
144-
RegExp get template {
145-
if (_paramRegexCache != null) return _paramRegexCache!;
146-
return _paramRegexCache = buildRegexFromTemplate(templateStr);
147-
}
150+
RegExp get template => buildRegexFromTemplate(templateStr);
148151

149152
@override
150-
Map<String, dynamic> resolveParams(String pattern) {
151-
final params = resolveParamsFromPath(template, pattern);
152-
final definitions =
153-
[this, ...subparts].where((e) => e.descriptors.isNotEmpty);
154-
if (definitions.isEmpty) return params;
153+
bool get terminal => _maybeTerminalPart.terminal;
155154

156-
for (final defn in definitions) {
157-
params[defn.name] = defn.descriptors.fold<dynamic>(
158-
params[defn.name],
159-
(value, descriptor) => descriptor(value),
155+
@override
156+
Map<String, dynamic>? resolveParams(String pattern) {
157+
final result = resolveParamsFromPath(template, pattern);
158+
if (result == null) return null;
159+
160+
for (final param in result.keys) {
161+
final defn = parts[param]!;
162+
final value = result[param];
163+
164+
result[param] = defn.descriptors.fold<dynamic>(
165+
value,
166+
(value, fn) => fn(value),
160167
);
161168
}
162169

163-
return params;
170+
return result;
171+
}
172+
173+
@override
174+
void addMiddleware<T>(IndexedValue<T> handler) {
175+
_maybeTerminalPart.addMiddleware(handler);
164176
}
177+
178+
@override
179+
void addRoute<T>(HTTPMethod method, IndexedValue<T> handler) =>
180+
_maybeTerminalPart.addRoute(method, handler);
181+
182+
@override
183+
IndexedValue? getHandler(HTTPMethod method) =>
184+
_maybeTerminalPart.getHandler(method);
185+
186+
@override
187+
bool hasMethod(HTTPMethod method) => _maybeTerminalPart.hasMethod(method);
188+
189+
@override
190+
Iterable<HTTPMethod> get methods => _maybeTerminalPart.methods;
165191
}

packages/spanner/lib/src/parametric/utils.dart

Lines changed: 54 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,19 @@ RegExp descriptorToRegex(String descriptor) {
3333
return RegExp(regexStr);
3434
}
3535

36+
String buildTemplateString({
37+
required String name,
38+
String? prefix,
39+
String? suffix,
40+
}) {
41+
var template = '<$name>';
42+
if (prefix != null) template = "$prefix$template";
43+
if (suffix != null) template = '$template$suffix';
44+
return template;
45+
}
46+
3647
RegExp buildRegexFromTemplate(String template) {
37-
String escapedTemplate = RegExp.escape(template);
48+
final escapedTemplate = RegExp.escape(template);
3849

3950
// Replace <...> placeholders with named capturing groups
4051
final regexPattern = escapedTemplate.replaceAllMapped(
@@ -48,30 +59,51 @@ RegExp buildRegexFromTemplate(String template) {
4859
return RegExp(regexPattern, caseSensitive: false);
4960
}
5061

51-
Map<String, dynamic> resolveParamsFromPath(RegExp templateRegex, String path) {
62+
Map<String, dynamic>? resolveParamsFromPath(
63+
RegExp templateRegex,
64+
String path,
65+
) {
5266
final match = templateRegex.firstMatch(path);
53-
if (match == null) return const {};
67+
if (match == null) return null;
5468

55-
final resolvedParams = <String, dynamic>{};
56-
for (final param in match.groupNames) {
57-
resolvedParams[param] = match.namedGroup(param);
58-
}
59-
return resolvedParams;
69+
return {
70+
for (final param in match.groupNames) param: match.namedGroup(param),
71+
};
6072
}
6173

6274
extension ParametricDefinitionsExtension on List<ParameterDefinition> {
63-
void sortByProps() {
64-
final Map<int, int> nullCount = {};
65-
for (final def in this) {
66-
int count = 0;
67-
if (def.prefix == null) count += 1;
68-
if (def.suffix == null) count += 1;
69-
nullCount[def.hashCode] = count;
70-
}
71-
72-
sort((a, b) => nullCount[a.hashCode]!.compareTo(nullCount[b.hashCode]!));
73-
}
74-
75-
Iterable get methods =>
76-
map((e) => e.methods).reduce((val, e) => {...val, ...e});
75+
void sortByProps() => sort((a, b) {
76+
// First, prioritize CompositeParameterDefinition
77+
if (a is CompositeParameterDefinition &&
78+
b is! CompositeParameterDefinition) {
79+
return -1;
80+
}
81+
if (b is CompositeParameterDefinition &&
82+
a is! CompositeParameterDefinition) {
83+
return 1;
84+
}
85+
86+
// If both are CompositeParameterDefinition, compare their lengths
87+
if (a is CompositeParameterDefinition &&
88+
b is CompositeParameterDefinition) {
89+
return b.parts.length.compareTo(a.parts.length);
90+
}
91+
92+
// Now handle SingleParameterDefn cases
93+
if (a is SingleParameterDefn && b is SingleParameterDefn) {
94+
bool aHasPrefix = a.prefix != null;
95+
bool aHasSuffix = a.suffix != null;
96+
bool bHasPrefix = b.prefix != null;
97+
bool bHasSuffix = b.suffix != null;
98+
99+
int aScore = (aHasPrefix ? 1 : 0) + (aHasSuffix ? 1 : 0);
100+
int bScore = (bHasPrefix ? 1 : 0) + (bHasSuffix ? 1 : 0);
101+
102+
return bScore.compareTo(aScore);
103+
}
104+
105+
// This case shouldn't occur if all elements are either Composite or Single,
106+
// but including it for completeness
107+
return 0;
108+
});
77109
}

0 commit comments

Comments
 (0)